Repository: microsoft/regorus (language: python)

Issue:
Build and test bindings in CI
Currently, the bindings are not regularly built in CI. This can lead to the bindings going out of date. We need to build the bindings with each PR job.

Ideally, there needs to be a way to test the bindings too. But not sure if that will bring in a lot of dependencies.

Add github workflows to test

- [x] C/C++ binding
- [x] C no_std binding
- [x] C# binding
- [x] Go binding
- [x] Java binding
- [x] Python bindings
- [x] Ruby bindings
- [x] WASM bindings


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml
index 1dc0a5a7..90ad5724 100644
--- a/.github/workflows/pr.yml
+++ b/.github/workflows/pr.yml
@@ -1,4 +1,4 @@
-name: Rust
+name: tests/release
 
 on:
   push:
@@ -10,26 +10,18 @@ env:
   CARGO_TERM_COLOR: always
 
 jobs:
-  build:
+  test:
 
     runs-on: ubuntu-latest
 
     steps:
-    - uses: actions/checkout@v3
-    - name: Add musl target
-      run: rustup target add x86_64-unknown-linux-musl
-    - name: Add no_std target
-      run: rustup target add thumbv7m-none-eabi
-    - name: Install musl-gcc
-      run: sudo apt update && sudo apt install -y musl-tools
+    - uses: actions/checkout@v4
     - name: Format Check
       run: cargo fmt --check
     - name: Build (all features)
-      run: cargo build -r --all-features --verbose
+      run: cargo build -r --all-features
     - name: Build
-      run: cargo build -r --verbose
-    - name: Build no_std
-      run: cd tests/ensure_no_std && cargo build -r --target thumbv7m-none-eabi
+      run: cargo build -r
     - name: Test no_std
       run: cargo test -r --no-default-features
     - name: Build only std
@@ -37,7 +29,7 @@ jobs:
     - name: Doc Tests
       run: cargo test -r --doc
     - name: Run tests
-      run: cargo test -r --verbose
+      run: cargo test -r
     - name: Run tests (ACI)
       run: cargo test -r --test aci
     - name: Run tests (KATA)
@@ -45,14 +37,3 @@ jobs:
     - name: Run tests (OPA Conformance)
       run: >-
         cargo test -r --test opa --features opa-testutil,serde_json/arbitrary_precision  -- $(tr '\n' ' ' < tests/opa.passing)
-    - name: Build (MUSL)
-      run: cargo build --verbose --all-targets --target x86_64-unknown-linux-musl
-    - name: Run tests (MUSL)
-      run: cargo test -r --verbose --target x86_64-unknown-linux-musl
-    - name: Run tests (MUSL ACI)
-      run: cargo test -r --test aci --target x86_64-unknown-linux-musl
-    - name: Run tests (KATA ACI)
-      run: cargo test -r --test kata --target x86_64-unknown-linux-musl
-    - name: Run tests (MUSL OPA Conformance)
-      run: >-
-        cargo test -r --test opa --features opa-testutil,serde_json/arbitrary_precision --target x86_64-unknown-linux-musl  -- $(tr '\n' ' ' < tests/opa.passing)
diff --git a/.github/workflows/publish-python.yml b/.github/workflows/publish-python.yml
index 5ff93124..29eac89a 100644
--- a/.github/workflows/publish-python.yml
+++ b/.github/workflows/publish-python.yml
@@ -23,7 +23,7 @@ jobs:
         with:
           python-version: '3.10'
       - name: Build wheels
-        uses: PyO3/maturin-action@v1
+        uses: PyO3/maturin-action@63b75c597b83e247fbf4fb7719801cc4220ae9f3 # v1.43.0
         with:
           target: ${{ matrix.target }}
           args: --release --out dist --find-interpreter --manifest-path bindings/python/Cargo.toml
@@ -47,7 +47,7 @@ jobs:
           python-version: '3.10'
           architecture: ${{ matrix.target }}
       - name: Build wheels
-        uses: PyO3/maturin-action@v1
+        uses: PyO3/maturin-action@63b75c597b83e247fbf4fb7719801cc4220ae9f3 # v1.43.0
         with:
           target: ${{ matrix.target }}
           args: --release --out dist --find-interpreter --manifest-path bindings/python/Cargo.toml
@@ -69,7 +69,7 @@ jobs:
         with:
           python-version: '3.10'
       - name: Build wheels
-        uses: PyO3/maturin-action@v1
+        uses: PyO3/maturin-action@63b75c597b83e247fbf4fb7719801cc4220ae9f3 # v1.43.0
         with:
           target: ${{ matrix.target }}
           args: --release --out dist --find-interpreter --manifest-path bindings/python/Cargo.toml
@@ -85,7 +85,7 @@ jobs:
     steps:
       - uses: actions/checkout@v3
       - name: Build sdist
-        uses: PyO3/maturin-action@v1
+        uses: PyO3/maturin-action@63b75c597b83e247fbf4fb7719801cc4220ae9f3 # v1.43.0
         with:
           command: sdist
           args: --out dist --manifest-path bindings/python/Cargo.toml
@@ -106,7 +106,7 @@ jobs:
         with:
           name: wheels
       - name: Publish to PyPI
-        uses: PyO3/maturin-action@v1
+        uses: PyO3/maturin-action@63b75c597b83e247fbf4fb7719801cc4220ae9f3 # v1.43.0
         env:
           MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
         with:
diff --git a/.github/workflows/release-plz.yml b/.github/workflows/release-plz.yml
index fcc8b3ef..c7de78b7 100644
--- a/.github/workflows/release-plz.yml
+++ b/.github/workflows/release-plz.yml
@@ -18,7 +18,7 @@ jobs:
       - name: Install Rust toolchain
         uses: dtolnay/rust-toolchain@stable
       - name: Run release-plz
-        uses: MarcoIeni/release-plz-action@v0.5
+        uses: MarcoIeni/release-plz-action@a10be9933e46ab5005d79ce8b1eafca2ecdcd844 #v0.3.68
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
           CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
diff --git a/bindings/c-nostd/CMakeLists.txt b/bindings/c-nostd/CMakeLists.txt
index 1d554bef..3cc4e93b 100644
--- a/bindings/c-nostd/CMakeLists.txt
+++ b/bindings/c-nostd/CMakeLists.txt
@@ -35,7 +35,7 @@ corrosion_import_crate(
   CRATE_TYPES staticlib FLAGS --crate-type=staticlib
   )
 
-add_executable(regorus_test_nostd main.c)
+add_executable(regorus_test main.c)
 # Add path to <regorus-source-folder>/bindings/ffi
-target_include_directories(regorus_test_nostd PRIVATE "../ffi")
-target_link_libraries(regorus_test_nostd regorus-ffi)
+target_include_directories(regorus_test PRIVATE "../ffi")
+target_link_libraries(regorus_test regorus-ffi)
diff --git a/bindings/c-nostd/main.c b/bindings/c-nostd/main.c
index 0ac88046..9cd54e71 100644
--- a/bindings/c-nostd/main.c
+++ b/bindings/c-nostd/main.c
@@ -7,13 +7,14 @@ char*  file_to_string(const char* file) {
     char * buffer = 0;
     long length;
     FILE * f = fopen (file, "rb");
-    
+
     if (f)
     {
 	fseek (f, 0, SEEK_END);
 	length = ftell (f);
 	fseek (f, 0, SEEK_SET);
-	buffer = malloc (length);
+	buffer = malloc (length + 1);
+	buffer[length] = '\0';
 	if (buffer)
 	{
 	    fread (buffer, 1, length, f);
@@ -26,8 +27,6 @@ char*  file_to_string(const char* file) {
 
 // If regorus is built with custom-allocator, then provide implementation.
 uint8_t* regorus_aligned_alloc(size_t alignment, size_t size) {
-    printf("%ld %ld\n", size, alignment);
-    fflush(stdout);
     return aligned_alloc(alignment, size);
 }
 
@@ -47,18 +46,21 @@ int main() {
     free(buffer);
     if (r.status != RegorusStatusOk)
 	goto error;
+    printf("Loaded policy %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);
     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);
     regorus_result_drop(r);
 
     // Add data
@@ -83,14 +85,14 @@ int main() {
     // Print output
     printf("%s", r.output);
     regorus_result_drop(r);
-    
-    
+
+
     // Free the engine.
     regorus_engine_drop(engine);
 
     return 0;
 error:
     printf("%s", r.error_message);
-	
+
     return 1;
 }
diff --git a/bindings/c/main.c b/bindings/c/main.c
index 53197989..17651ba6 100644
--- a/bindings/c/main.c
+++ b/bindings/c/main.c
@@ -10,16 +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);
     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);
     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);
     regorus_result_drop(r);
 
     // Add data
diff --git a/bindings/cpp/CMakeLists.txt b/bindings/cpp/CMakeLists.txt
index 18ce8fb8..a510438b 100644
--- a/bindings/cpp/CMakeLists.txt
+++ b/bindings/cpp/CMakeLists.txt
@@ -19,8 +19,14 @@ corrosion_import_crate(
   MANIFEST_PATH "../ffi/Cargo.toml"
   # Always build regorus in Release mode.
   PROFILE "release"
-  # Only build the "regorusc" crate.
-  CRATES "regorus-ffi")
+  # Only build the "regorus-ffi" crate.
+  CRATES "regorus-ffi"
+
+  # Select specific features in regorus.
+  FEATURES "regorus/semver"
+
+  # Link statically
+  CRATE_TYPES "staticlib")
 
 add_executable(regorus_test main.cpp)
 # Add path to <regorus-source-folder>/bindings/ffi
diff --git a/bindings/cpp/main.cpp b/bindings/cpp/main.cpp
index 7706e1bf..08145249 100644
--- a/bindings/cpp/main.cpp
+++ b/bindings/cpp/main.cpp
@@ -89,6 +89,7 @@ int main() {
 	    std::cerr<<result.error()<<std::endl;
 	    return -1;
 	}
+	std::cout<<"Loaded policy "<<result.output()<< std::endl;
     }
     {
 	auto result = engine.add_data_from_json_file("../../../tests/aci/data.json");
diff --git a/bindings/ffi/Cargo.toml b/bindings/ffi/Cargo.toml
index 419f7a02..55fab98e 100644
--- a/bindings/ffi/Cargo.toml
+++ b/bindings/ffi/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "regorus-ffi"
-version = "0.1.0"
+version = "0.1.5"
 edition = "2021"
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -13,7 +13,7 @@ regorus = { path  = "../..", default-features = false }
 serde_json = "1.0.113"
 
 [features]
-default = ["std"]
+default = ["std", "regorus/full-opa"]
 std = ["regorus/std"]
 custom_allocator = []
 
diff --git a/bindings/ffi/build.rs b/bindings/ffi/build.rs
index a48986d3..5c0ca06d 100644
--- a/bindings/ffi/build.rs
+++ b/bindings/ffi/build.rs
@@ -24,7 +24,7 @@ fn main() {
 
     csbindgen::Builder::default()
         .input_extern_file("src/lib.rs")
-        .csharp_dll_name("regorusc")
+        .csharp_dll_name("regorus_ffi")
         .csharp_class_name("API")
         .csharp_namespace("RegorusFFI")
         .generate_csharp_file("./RegorusFFI.g.cs")
diff --git a/bindings/ffi/src/lib.rs b/bindings/ffi/src/lib.rs
index 1422161e..890214f1 100644
--- a/bindings/ffi/src/lib.rs
+++ b/bindings/ffi/src/lib.rs
@@ -39,14 +39,14 @@ fn to_c_str(s: String) -> *mut c_char {
     }
 }
 
-fn from_c_str(s: *const c_char) -> Result<String> {
+fn from_c_str(name: &str, s: *const c_char) -> Result<String> {
     if s.is_null() {
         bail!("null pointer");
     }
     unsafe {
         CStr::from_ptr(s)
             .to_str()
-            .map_err(|_| anyhow!("`path`: invalid utf8"))
+            .map_err(|e| anyhow!("`{name}`: invalid utf8.\n{e}"))
             .map(|s| s.to_string())
     }
 }
@@ -70,6 +70,21 @@ fn to_regorus_result(r: Result<()>) -> RegorusResult {
     }
 }
 
+fn to_regorus_string_result(r: Result<String>) -> RegorusResult {
+    match r {
+        Ok(s) => RegorusResult {
+            status: RegorusStatus::RegorusStatusOk,
+            output: to_c_str(s),
+            error_message: std::ptr::null_mut(),
+        },
+        Err(e) => RegorusResult {
+            status: RegorusStatus::RegorusStatusError,
+            output: std::ptr::null_mut(),
+            error_message: to_c_str(format!("{e}")),
+        },
+    }
+}
+
 /// Wrapper for `regorus::Engine`.
 #[derive(Clone)]
 pub struct RegorusEngine {
@@ -81,10 +96,13 @@ pub struct RegorusEngine {
 /// `output` and `error_message` strings are not valid after drop.
 #[no_mangle]
 pub extern "C" fn regorus_result_drop(r: RegorusResult) {
-    if !r.error_message.is_null() {
-        unsafe {
+    unsafe {
+        if !r.error_message.is_null() {
             let _ = CString::from_raw(r.error_message);
         }
+        if !r.output.is_null() {
+            let _ = CString::from_raw(r.output);
+        }
     }
 }
 
@@ -133,10 +151,10 @@ pub extern "C" fn regorus_engine_add_policy(
     path: *const c_char,
     rego: *const c_char,
 ) -> RegorusResult {
-    to_regorus_result(|| -> Result<()> {
+    to_regorus_string_result(|| -> Result<String> {
         to_ref(&engine)?
             .engine
-            .add_policy(from_c_str(path)?, from_c_str(rego)?)
+            .add_policy(from_c_str("path", path)?, from_c_str("rego", rego)?)
     }())
 }
 
@@ -146,10 +164,10 @@ pub extern "C" fn regorus_engine_add_policy_from_file(
     engine: *mut RegorusEngine,
     path: *const c_char,
 ) -> RegorusResult {
-    to_regorus_result(|| -> Result<()> {
+    to_regorus_string_result(|| -> Result<String> {
         to_ref(&engine)?
             .engine
-            .add_policy_from_file(from_c_str(path)?)
+            .add_policy_from_file(from_c_str("path", path)?)
     }())
 }
 
@@ -165,7 +183,7 @@ pub extern "C" fn regorus_engine_add_data_json(
     to_regorus_result(|| -> Result<()> {
         to_ref(&engine)?
             .engine
-            .add_data(regorus::Value::from_json_str(&from_c_str(data)?)?)
+            .add_data(regorus::Value::from_json_str(&from_c_str("data", data)?)?)
     }())
 }
 
@@ -178,7 +196,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)?)?)
+            .add_data(regorus::Value::from_json_file(&from_c_str("path", path)?)?)
     }())
 }
 
@@ -205,7 +223,7 @@ pub extern "C" fn regorus_engine_set_input_json(
     to_regorus_result(|| -> Result<()> {
         to_ref(&engine)?
             .engine
-            .set_input(regorus::Value::from_json_str(&from_c_str(input)?)?);
+            .set_input(regorus::Value::from_json_str(&from_c_str("input", input)?)?);
         Ok(())
     }())
 }
@@ -219,7 +237,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)?)?);
+            .set_input(regorus::Value::from_json_file(&from_c_str("path", path)?)?);
         Ok(())
     }())
 }
@@ -236,7 +254,7 @@ pub extern "C" fn regorus_engine_eval_query(
     let output = || -> Result<String> {
         let results = to_ref(&engine)?
             .engine
-            .eval_query(from_c_str(query)?, false)?;
+            .eval_query(from_c_str("query", query)?, false)?;
         Ok(serde_json::to_string_pretty(&results)?)
     }();
     match output {
diff --git a/bindings/go/go.mod b/bindings/go/go.mod
index 2f0af35c..17349044 100644
--- a/bindings/go/go.mod
+++ b/bindings/go/go.mod
@@ -1,3 +1,3 @@
-module regorus-test
+module regorus_test
 
 go 1.21.5
diff --git a/bindings/go/main.go b/bindings/go/main.go
index d00837ea..05c8a1a3 100644
--- a/bindings/go/main.go
+++ b/bindings/go/main.go
@@ -3,7 +3,7 @@ package main
 import (
 	"fmt"
 	"os"
-	"regorus-test/pkg/regorus"
+	"regorus_test/pkg/regorus"
 	"time"
 )
 
diff --git a/bindings/java/Cargo.toml b/bindings/java/Cargo.toml
index 57471aa4..33b1840f 100644
--- a/bindings/java/Cargo.toml
+++ b/bindings/java/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "regorus-java"
-version = "0.1.0"
+version = "0.1.5"
 edition = "2021"
 repository = "https://github.com/microsoft/regorus/bindings/java"
 description = "Java bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
diff --git a/bindings/java/README.md b/bindings/java/README.md
index 8918c094..608ea39d 100644
--- a/bindings/java/README.md
+++ b/bindings/java/README.md
@@ -30,7 +30,7 @@ You then need to build Java bindings using:
 $ mvn package
 ```
 
-And you will have a JAR at `./target/regorus-java-0.0.1.jar`.
+And you will have a JAR at `./target/regorus-java-0.1.5.jar`.
 
 ## Usage
 
@@ -60,10 +60,10 @@ You need to ensure artifacts built in [previous section](#building) are in Java'
 
 For example with `java` CLI:
 ```bash
-$ java -Djava.library.path=../../target/aarch64-apple-darwin/release/ -cp target/regorus-java-0.0.1.jar Test.java
+$ java -Djava.library.path=../../target/aarch64-apple-darwin/release/ -cp target/regorus-java-0.1.5.jar Test.java
 ```
 
 should gave you the output:
 ```
 {"result":[{"expressions":[{"value":"Hello, World!","text":"data.test.message","location":{"row":1,"col":1}}]}]}
-```
\ No newline at end of file
+```
diff --git a/bindings/java/Test.java b/bindings/java/Test.java
new file mode 100644
index 00000000..c36e7fb6
--- /dev/null
+++ b/bindings/java/Test.java
@@ -0,0 +1,20 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+import com.microsoft.regorus.Engine;
+
+public class Test {
+    public static void main(String[] args) {
+        try (Engine engine = new Engine()) {
+            engine.addPolicy(
+                "hello.rego",
+                "package test\nmessage = concat(\", \", [input.message, data.message])"
+            );
+            engine.addDataJson("{\"message\":\"World!\"}");
+            engine.setInputJson("{\"message\":\"Hello\"}");
+            String resJson = engine.evalQuery("data.test.message");
+
+            System.out.println(resJson);
+        }
+    }
+}
diff --git a/bindings/java/pom.xml b/bindings/java/pom.xml
index 6b5ed292..fe8981e4 100644
--- a/bindings/java/pom.xml
+++ b/bindings/java/pom.xml
@@ -9,7 +9,7 @@
 
   <groupId>com.microsoft.regorus</groupId>
   <artifactId>regorus-java</artifactId>
-  <version>0.0.1</version>
+  <version>0.1.5</version>
 
   <name>Regorus Java</name>
   <description>Java bindings for Regorus - a fast, lightweight Rego interpreter written in Rust</description>
diff --git a/bindings/python/Cargo.toml b/bindings/python/Cargo.toml
index e47e29ee..d62f688d 100644
--- a/bindings/python/Cargo.toml
+++ b/bindings/python/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "regoruspy"
-version = "0.1.0"
+version = "0.1.5"
 edition = "2021"
 repository = "https://github.com/microsoft/regorus/bindings/python"
 description = "Python bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
diff --git a/bindings/python/src/lib.rs b/bindings/python/src/lib.rs
index ee1dc93d..4502cddc 100644
--- a/bindings/python/src/lib.rs
+++ b/bindings/python/src/lib.rs
@@ -33,12 +33,12 @@ impl Clone for Engine {
     }
 }
 
-fn from<'source>(ob: &'source PyAny) -> Result<Value, PyErr> {
+fn from<'source>(ob: &Bound<'_, PyAny>) -> Result<Value, PyErr> {
     // dicts
     Ok(if let Ok(dict) = ob.downcast::<PyDict>() {
         let mut map = BTreeMap::new();
         for (k, v) in dict {
-            map.insert(from(k)?, from(v)?);
+            map.insert(from(&k)?, from(&v)?);
         }
         map.into()
     }
@@ -46,7 +46,7 @@ fn from<'source>(ob: &'source PyAny) -> Result<Value, PyErr> {
     else if let Ok(pset) = ob.downcast::<PySet>() {
         let mut set = BTreeSet::new();
         for v in pset {
-            set.insert(from(v)?);
+            set.insert(from(&v)?);
         }
         set.into()
     }
@@ -55,7 +55,7 @@ fn from<'source>(ob: &'source PyAny) -> Result<Value, PyErr> {
         //
         let mut set = BTreeSet::new();
         for v in pfset {
-            set.insert(from(v)?);
+            set.insert(from(&v)?);
         }
         set.into()
     }
@@ -63,30 +63,30 @@ fn from<'source>(ob: &'source PyAny) -> Result<Value, PyErr> {
     else if let Ok(plist) = ob.downcast::<PyList>() {
         let mut array = Vec::new();
         for v in plist {
-            array.push(from(v)?);
+            array.push(from(&v)?);
         }
         array.into()
     } else if let Ok(ptuple) = ob.downcast::<PyTuple>() {
         let mut array = Vec::new();
         for v in ptuple {
-            array.push(from(v)?);
+            array.push(from(&v)?);
         }
         array.into()
     }
     // String
-    else if let Ok(s) = String::extract(ob) {
+    else if let Ok(s) = ob.extract::<String>() {
         s.into()
     }
     // Numeric
-    else if let Ok(v) = i64::extract(ob) {
+    else if let Ok(v) = ob.extract::<i64>() {
         v.into()
-    } else if let Ok(v) = u64::extract(ob) {
+    } else if let Ok(v) = ob.extract::<u64>() {
         v.into()
-    } else if let Ok(v) = f64::extract(ob) {
+    } else if let Ok(v) = ob.extract::<f64>() {
         v.into()
     }
     // Boolean
-    else if let Ok(b) = bool::extract(ob) {
+    else if let Ok(b) = ob.extract::<bool>() {
         b.into()
     }
     // None
@@ -97,7 +97,7 @@ fn from<'source>(ob: &'source PyAny) -> Result<Value, PyErr> {
     else if let Ok(pseq) = ob.downcast::<PySequence>() {
         let mut array = Vec::new();
         for i in 0..pseq.len()? {
-            array.push(from(pseq.get_item(i)?)?);
+            array.push(from(&pseq.get_item(i)?)?);
         }
         array.into()
     }
@@ -109,7 +109,7 @@ fn from<'source>(ob: &'source PyAny) -> Result<Value, PyErr> {
         for i in 0..keys.len()? {
             let key = keys.get_item(i)?;
             let value = values.get_item(i)?;
-            map.insert(from(key)?, from(value)?);
+            map.insert(from(&key)?, from(&value)?);
         }
         map.into()
     } else {
@@ -140,7 +140,7 @@ fn to(mut v: Value, py: Python<'_>) -> Result<PyObject> {
         }
 
         Value::Array(_) => {
-            let list = PyList::empty(py);
+            let list = PyList::empty_bound(py);
             for v in std::mem::replace(v.as_array_mut()?, Vec::new()) {
                 list.append(to(v, py)?)?;
             }
@@ -148,7 +148,7 @@ fn to(mut v: Value, py: Python<'_>) -> Result<PyObject> {
         }
 
         Value::Set(_) => {
-            let set = PySet::empty(py)?;
+            let set = PySet::empty_bound(py)?;
             for v in std::mem::replace(v.as_set_mut()?, BTreeSet::new()) {
                 set.add(to(v, py)?)?;
             }
@@ -156,7 +156,7 @@ fn to(mut v: Value, py: Python<'_>) -> Result<PyObject> {
         }
 
         Value::Object(_) => {
-            let dict = PyDict::new(py);
+            let dict = PyDict::new_bound(py);
             for (k, v) in std::mem::replace(v.as_object_mut()?, BTreeMap::new()) {
                 dict.set_item(to(k, py)?, to(v, py)?)?;
             }
@@ -181,7 +181,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 add_policy(&mut self, path: String, rego: String) -> Result<String> {
         self.engine.add_policy(path, rego)
     }
 
@@ -190,15 +190,21 @@ impl Engine {
     /// The policy is parsed into AST.
     ///
     /// * `path`: Path to the policy file.
-    pub fn add_policy_from_file(&mut self, path: String) -> Result<()> {
+    pub fn add_policy_from_file(&mut self, path: String) -> Result<String> {
         self.engine.add_policy_from_file(path)
     }
 
+    /// Get the list of packages defined by loaded policies.
+    ///
+    pub fn get_packages(&mut self) -> Result<Vec<String>> {
+        self.engine.get_packages()
+    }
+
     /// Add policy data.
     ///
     /// * `data`: Rego value. A Rego value is a number, bool, string, None
     ///           or a list/set/map whose items themselves are Rego values.
-    pub fn add_data(&mut self, data: &PyAny) -> Result<()> {
+    pub fn add_data(&mut self, data: &Bound<'_, PyAny>) -> Result<()> {
         let data = from(data)?;
         self.engine.add_data(data)
     }
@@ -229,7 +235,7 @@ impl Engine {
     ///
     /// * `input`: Rego value. A Rego value is a number, bool, string, None
     ///            or a list/set/map whose items themselves are Rego values.
-    pub fn set_input(&mut self, input: &PyAny) -> Result<()> {
+    pub fn set_input(&mut self, input: &Bound<'_, PyAny>) -> Result<()> {
         let input = from(input)?;
         self.engine.set_input(input);
         Ok(())
@@ -259,17 +265,17 @@ impl Engine {
     pub fn eval_query(&mut self, query: String, py: Python<'_>) -> Result<PyObject> {
         let results = self.engine.eval_query(query, false)?;
 
-        let rlist = PyList::empty(py);
+        let rlist = PyList::empty_bound(py);
         for result in results.result.into_iter() {
-            let rdict = PyDict::new(py);
+            let rdict = PyDict::new_bound(py);
 
-            let elist = PyList::empty(py);
+            let elist = PyList::empty_bound(py);
             for expr in result.expressions.into_iter() {
-                let edict = PyDict::new(py);
+                let edict = PyDict::new_bound(py);
                 edict.set_item("value".to_object(py), to(expr.value, py)?)?;
                 edict.set_item("text".to_object(py), expr.text.as_ref().to_object(py))?;
 
-                let ldict = PyDict::new(py);
+                let ldict = PyDict::new_bound(py);
                 ldict.set_item("row".to_object(py), expr.location.row.to_object(py))?;
                 ldict.set_item("col".to_object(py), expr.location.col.to_object(py))?;
 
@@ -281,7 +287,7 @@ impl Engine {
             rdict.set_item("bindings".to_object(py), to(result.bindings, py)?)?;
             rlist.append(rdict)?;
         }
-        let dict = PyDict::new(py);
+        let dict = PyDict::new_bound(py);
         dict.set_item("result".to_object(py), rlist)?;
         Ok(dict.into())
     }
@@ -296,6 +302,6 @@ impl Engine {
 }
 
 #[pymodule]
-pub fn regorus(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
+pub fn regorus(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
     m.add_class::<crate::Engine>()
 }
diff --git a/bindings/ruby/ext/regorusrb/Cargo.toml b/bindings/ruby/ext/regorusrb/Cargo.toml
index 180b5c0e..cf6574b6 100644
--- a/bindings/ruby/ext/regorusrb/Cargo.toml
+++ b/bindings/ruby/ext/regorusrb/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "regorusrb"
-version = "0.1.0"
+version = "0.1.5"
 edition = "2021"
 description = "Ruby bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
 publish = false
diff --git a/bindings/wasm/Cargo.toml b/bindings/wasm/Cargo.toml
index 03ad8358..ba588e03 100644
--- a/bindings/wasm/Cargo.toml
+++ b/bindings/wasm/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "regorusjs"
-version = "0.1.0"
+version = "0.1.5"
 edition = "2021"
 repository = "https://github.com/microsoft/regorus/bindings/wasm"
 description = "WASM bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs
index 08435f57..397bb74f 100644
--- a/bindings/wasm/src/lib.rs
+++ b/bindings/wasm/src/lib.rs
@@ -50,7 +50,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<(), JsValue> {
+    pub fn add_policy(&mut self, path: String, rego: String) -> Result<String, JsValue> {
         self.engine.add_policy(path, rego).map_err(error_to_jsvalue)
     }
 


Test patch:
diff --git a/.github/workflows/test-c-cpp.yml b/.github/workflows/test-c-cpp.yml
new file mode 100644
index 00000000..609fee3f
--- /dev/null
+++ b/.github/workflows/test-c-cpp.yml
@@ -0,0 +1,43 @@
+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: Setup gcc, g++, cmake, ninja
+        run: sudo apt update && sudo apt install -y gcc g++ cmake ninja-build
+
+      - name: Test c binding
+        run: |
+          mkdir bindings/c/build
+          cd bindings/c/build
+          cmake -G Ninja ..
+          ninja
+          ./regorus_test
+
+      - name: Test c-nostd binding
+        run: |
+          mkdir bindings/c-nostd/build
+          cd bindings/c-nostd/build
+          cmake -G Ninja ..
+          ninja
+          ./regorus_test
+
+      - name: Test cpp binding
+        run: |
+          mkdir bindings/cpp/build
+          cd bindings/cpp/build
+          cmake -G Ninja ..
+          ninja
+          ./regorus_test
diff --git a/.github/workflows/test-csharp.yml b/.github/workflows/test-csharp.yml
new file mode 100644
index 00000000..67f16d1b
--- /dev/null
+++ b/.github/workflows/test-csharp.yml
@@ -0,0 +1,26 @@
+name: bindings/csharp
+
+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
+
+      - uses: actions/setup-dotnet@v4
+
+      - name: Build
+        run: dotnet build
+        working-directory: ./bindings/csharp
+
+      - name: Run
+        run: LD_LIBRARY_PATH=. dotnet run
+        working-directory: ./bindings/csharp
diff --git a/.github/workflows/test-go.yml b/.github/workflows/test-go.yml
new file mode 100644
index 00000000..320e0b1b
--- /dev/null
+++ b/.github/workflows/test-go.yml
@@ -0,0 +1,33 @@
+name: bindings/go
+
+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
+   
+          
+      - uses: actions/setup-go@v5
+        with:
+          python-version: ${{ env.PYTHON_VERSION }}
+          architecture: x64
+
+      - name: Build ffi
+        run: cargo build -r
+        working-directory: ./bindings/ffi
+
+      - name: Test go
+        run: |
+          go mod tidy
+          go build
+          LD_LIBRARY_PATH=../../target/release ./regorus_test
+        working-directory: ./bindings/go
diff --git a/.github/workflows/test-java.yml b/.github/workflows/test-java.yml
new file mode 100644
index 00000000..182287c0
--- /dev/null
+++ b/.github/workflows/test-java.yml
@@ -0,0 +1,35 @@
+name: bindings/java
+
+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
+
+      - uses: actions/setup-java@v4
+        with:
+          java-version: 8
+          distribution: "corretto"
+      - uses: dtolnay/rust-toolchain@stable
+
+      - name: Building binding
+        run: cargo build --release --manifest-path bindings/java/Cargo.toml
+
+      - name: Build jar
+        run: mvn package
+        working-directory: ./bindings/java
+
+      - name: Test jar
+        run: |
+          javac -cp target/regorus-java-0.1.5.jar Test.java
+          java -Djava.library.path=../../target/release -cp target/regorus-java-0.1.5.jar:. Test
+        working-directory: ./bindings/java
diff --git a/.github/workflows/test-musl.yml b/.github/workflows/test-musl.yml
new file mode 100644
index 00000000..c848b4a2
--- /dev/null
+++ b/.github/workflows/test-musl.yml
@@ -0,0 +1,33 @@
+name: musl
+
+on:
+  push:
+    branches: [ "main" ]
+  pull_request:
+    branches: [ "main" ]
+
+env:
+  CARGO_TERM_COLOR: always
+
+jobs:
+  test:
+
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v3
+    - name: Add musl target
+      run: rustup target add x86_64-unknown-linux-musl
+    - name: Install musl-gcc
+      run: sudo apt update && sudo apt install -y musl-tools
+    - name: Build (MUSL)
+      run: cargo build --verbose --all-targets --target x86_64-unknown-linux-musl
+    - name: Run tests (MUSL)
+      run: cargo test -r --verbose --target x86_64-unknown-linux-musl
+    - name: Run tests (MUSL ACI)
+      run: cargo test -r --test aci --target x86_64-unknown-linux-musl
+    - name: Run tests (KATA ACI)
+      run: cargo test -r --test kata --target x86_64-unknown-linux-musl
+    - name: Run tests (MUSL OPA Conformance)
+      run: >-
+        cargo test -r --test opa --features opa-testutil,serde_json/arbitrary_precision --target x86_64-unknown-linux-musl  -- $(tr '\n' ' ' < tests/opa.passing)
diff --git a/.github/workflows/test-no-std.yml b/.github/workflows/test-no-std.yml
new file mode 100644
index 00000000..78ad1b4f
--- /dev/null
+++ b/.github/workflows/test-no-std.yml
@@ -0,0 +1,24 @@
+name: bindings/no-std
+
+on:
+  push:
+    branches: [ "main" ]
+  pull_request:
+    branches: [ "main" ]
+
+env:
+  CARGO_TERM_COLOR: always
+
+jobs:
+  build:
+
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v3
+    - name: Add no_std target
+      run: rustup target add thumbv7m-none-eabi
+    - name: Build
+      run: cargo build -r --target thumbv7m-none-eabi
+      working-directory: ./tests/ensure_no_std
+
diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml
new file mode 100644
index 00000000..84a434d8
--- /dev/null
+++ b/.github/workflows/test-python.yml
@@ -0,0 +1,37 @@
+name: bindings/python
+
+on:
+  push:
+    branches: [ "main" ]
+  pull_request:
+    branches: [ "main" ]
+
+env:
+  PYTHON_VERSION: "3.10"
+    
+jobs:
+  test:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+        with:
+          fetch-depth: 0
+
+      - uses: actions/setup-python@v4
+        with:
+          python-version: ${{ env.PYTHON_VERSION }}
+          architecture: x64
+
+      - name: Build wheels
+        uses: PyO3/maturin-action@63b75c597b83e247fbf4fb7719801cc4220ae9f3 # v1.43.0
+        with:
+          target: x86_64
+          args: --release --out dist --manifest-path bindings/python/Cargo.toml
+          sccache: 'true'
+
+      - name: Test wheel
+        run:  |
+          pip3 install dist/regorus-*.whl
+          cd bindings/python
+          python3 test.py          
diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml
index 16acf741..298e8bc0 100644
--- a/.github/workflows/test-ruby.yml
+++ b/.github/workflows/test-ruby.yml
@@ -1,4 +1,4 @@
-name: test-ruby
+name: bindings/ruby
 
 on:
   push:
@@ -7,7 +7,7 @@ on:
     branches: [ "main" ]
 
 jobs:
-  setup:
+  test:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout repository
@@ -16,7 +16,7 @@ jobs:
           fetch-depth: 0
 
       - name: Setup Ruby and Rust
-        uses: oxidize-rb/actions/setup-ruby-and-rust@v1
+        uses: oxidize-rb/actions/setup-ruby-and-rust@7ca44a16e287e5ff7dd72ab53f4bd41cbf34a571 #v1.26
         with:
           ruby-version: "3.3.1"
           rustup-toolchain: "stable"
diff --git a/.github/workflows/test-wasm.yml b/.github/workflows/test-wasm.yml
new file mode 100644
index 00000000..1a5e309e
--- /dev/null
+++ b/.github/workflows/test-wasm.yml
@@ -0,0 +1,30 @@
+name: bindings/wasm
+
+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: Setup Node
+        uses: actions/setup-node@v4
+        with:
+          node-version: 18
+
+      - name: Install wasmlpack
+        run: cargo install wasm-pack
+
+      - name: Test wasm binding
+        run: |
+          cd bindings/wasm
+          wasm-pack build --target nodejs --release
+          node test.js
diff --git a/.github/workflows/tests-debug.yml b/.github/workflows/tests-debug.yml
new file mode 100644
index 00000000..873c123d
--- /dev/null
+++ b/.github/workflows/tests-debug.yml
@@ -0,0 +1,37 @@
+name: tests/debug
+
+on:
+  push:
+    branches: [ "main" ]
+  pull_request:
+    branches: [ "main" ]
+
+env:
+  CARGO_TERM_COLOR: always
+
+jobs:
+  test:
+
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v4
+    - name: Build (all features)
+      run: cargo build --all-features
+    - name: Build
+      run: cargo build
+    - name: Test no_std
+      run: cargo test --no-default-features
+    - name: Build only std
+      run: cargo build --example regorus --no-default-features --features "std"
+    - name: Doc Tests
+      run: cargo test --doc
+    - name: Run tests
+      run: cargo test
+    - name: Run tests (ACI)
+      run: cargo test --test aci
+    - name: Run tests (KATA)
+      run: cargo test --test kata
+    - name: Run tests (OPA Conformance)
+      run: >-
+        cargo test --test opa --features opa-testutil,serde_json/arbitrary_precision  -- $(tr '\n' ' ' < tests/opa.passing)
diff --git a/bindings/csharp/regorus-test.csproj b/bindings/csharp/regorus-test.csproj
index ddf818d6..0267fedf 100644
--- a/bindings/csharp/regorus-test.csproj
+++ b/bindings/csharp/regorus-test.csproj
@@ -1,4 +1,4 @@
-﻿<Project Sdk="Microsoft.NET.Sdk"> 
+﻿<Project Sdk="Microsoft.NET.Sdk" InitialTargets="BuildRegorusFFI"> 
 
   <Target Name="BuildRegorusFFI">
     <Exec Command="cargo build -r --manifest-path ../ffi/Cargo.toml" />
diff --git a/bindings/python/test.py b/bindings/python/test.py
index 9cf0d592..014a77fc 100644
--- a/bindings/python/test.py
+++ b/bindings/python/test.py
@@ -7,9 +7,14 @@
 engine = regorus.Engine()
 
 # Load policies
-engine.add_policy_from_file('../../tests/aci/framework.rego')
-engine.add_policy_from_file('../../tests/aci/api.rego')
-engine.add_policy_from_file('../../tests/aci/policy.rego')
+pkg = engine.add_policy_from_file('../../tests/aci/framework.rego')
+print(' Loaded Policy %s' % pkg)
+
+pkg = engine.add_policy_from_file('../../tests/aci/api.rego')
+print(' Loaded Policy %s' % pkg)
+
+pkg = engine.add_policy_from_file('../../tests/aci/policy.rego')
+print(' Loaded Policy %s' % pkg)
 
 # Add policy data
 data = {
diff --git a/bindings/wasm/test.js b/bindings/wasm/test.js
index bd2d7ddc..a0304bc2 100644
--- a/bindings/wasm/test.js
+++ b/bindings/wasm/test.js
@@ -7,7 +7,7 @@ var regorus = require('./pkg/regorusjs')
 var engine = new regorus.Engine();
 
 // Add Rego policy.
-engine.add_policy(
+var pkg = engine.add_policy(
     // Associate this file name with policy
     'hello.rego',
     
@@ -18,6 +18,7 @@ engine.add_policy(
   # Join messages
   message = concat(", ", [input.message, data.message])
 `)
+console.log("Loaded policy " + pkg)
 
 // Set policy data
 engine.add_data_json(`
@@ -34,7 +35,7 @@ engine.set_input_json(`
 `)
 
 // Eval query
-results = engine.eval_query('data.test.message')
+var results = engine.eval_query('data.test.message')
 
 // Display
 console.log(results)