Repository: microsoft/regorus (language: python)

Issue:
[binding] C# binding
Implement C# binding for regorus
[binding] C binding 
Implement C binding for Regorus


Discussion / hints:

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index 11447b04..e9be08ed 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,7 @@
 [workspace]
 
 members = [
+    "bindings/ffi",
     "bindings/python",
     "bindings/wasm"
 ]
diff --git a/bindings/c/CMakeLists.txt b/bindings/c/CMakeLists.txt
new file mode 100644
index 00000000..df3a49d9
--- /dev/null
+++ b/bindings/c/CMakeLists.txt
@@ -0,0 +1,27 @@
+# Copyright (c) Microsoft
+# Licensed under the MIT License.
+
+cmake_minimum_required(VERSION 3.12 FATAL_ERROR)
+include(FetchContent)
+
+FetchContent_Declare(
+    Corrosion
+    GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git
+    GIT_TAG v0.4 # Optionally specify a commit hash, version tag or branch here
+)
+FetchContent_MakeAvailable(Corrosion)
+
+project("regorus-test-c")
+
+corrosion_import_crate(
+  # Path to <regorus-source-folder>/bindings/ffi/Cargo.toml
+  MANIFEST_PATH "../ffi/Cargo.toml"
+  # Always build regorus in Release mode.
+  PROFILE "release"
+  # Only build the "regorusc" crate.
+  CRATES "regorus-ffi")
+
+add_executable(regorus_test main.c)
+# Add path to <regorus-source-folder>/bindings/ffi
+target_include_directories(regorus_test PRIVATE "../ffi")
+target_link_libraries(regorus_test regorus-ffi)
diff --git a/bindings/c/main.c b/bindings/c/main.c
new file mode 100644
index 00000000..53197989
--- /dev/null
+++ b/bindings/c/main.c
@@ -0,0 +1,55 @@
+#include <stdio.h>
+#include "regorus.h"
+
+int main() {
+    // Create engine.
+    RegorusEngine* engine = regorus_engine_new();
+    RegorusResult r;
+
+    // Load policies.
+    r = regorus_engine_add_policy_from_file(engine, "../../../tests/aci/framework.rego");
+    if (r.status != RegorusStatusOk)
+	goto error;
+    regorus_result_drop(r);
+
+    r = regorus_engine_add_policy_from_file(engine, "../../../tests/aci/api.rego");
+    if (r.status != RegorusStatusOk)
+	goto error;
+    regorus_result_drop(r);
+    
+    r = regorus_engine_add_policy_from_file(engine, "../../../tests/aci/policy.rego");
+    if (r.status != RegorusStatusOk)
+	goto error;
+    regorus_result_drop(r);
+
+    // Add data
+    r = regorus_engine_add_data_from_json_file(engine, "../../../tests/aci/data.json");
+    if (r.status != RegorusStatusOk)
+	goto error;
+    regorus_result_drop(r);
+
+    // Set input
+    r = regorus_engine_set_input_from_json_file(engine, "../../../tests/aci/input.json");
+    if (r.status != RegorusStatusOk)
+	goto error;
+    regorus_result_drop(r);
+
+    // Eval query
+    r = regorus_engine_eval_query(engine, "data.framework.mount_overlay=x");
+    if (r.status != RegorusStatusOk)
+	goto error;
+
+    // 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/csharp/Program.cs b/bindings/csharp/Program.cs
new file mode 100644
index 00000000..702ecaa2
--- /dev/null
+++ b/bindings/csharp/Program.cs
@@ -0,0 +1,51 @@
+﻿using System.Diagnostics;
+
+long nanosecPerTick = (1000L*1000L*1000L) / Stopwatch.Frequency;
+var w = new Stopwatch();
+
+
+// Force load of modules.
+{
+    var _e = new Regorus.Engine();
+    var _j = System.Text.Json.JsonDocument.Parse("{}");
+}
+
+w.Restart();
+
+var engine = new Regorus.Engine();
+
+w.Stop();
+var newEngineTicks = w.ElapsedTicks;
+
+
+w.Restart();
+
+// Load policies and data.
+engine.AddPolicyFromFile("../../tests/aci/framework.rego");
+engine.AddPolicyFromFile("../../tests/aci/api.rego");
+engine.AddPolicyFromFile("../../tests/aci/policy.rego");
+engine.AddDataFromJsonFile("../../tests/aci/data.json");
+
+
+w.Stop();
+var loadPoliciesTicks = w.ElapsedTicks;
+
+
+w.Restart();
+
+// Set input and eval query.
+engine.SetInputFromJsonFile("../../tests/aci/input.json");
+var results = engine.EvalQuery("data.framework.mount_overlay = x");
+var resultsDoc = System.Text.Json.JsonDocument.Parse(results);
+
+w.Stop();
+var evalTicks = w.ElapsedTicks;
+
+Console.WriteLine("{0}", results);
+
+
+Console.WriteLine("Engine creation took {0} msecs", (newEngineTicks*nanosecPerTick)/(1000.0*1000.0));
+Console.WriteLine("Load policies and data took {0} msecs", (loadPoliciesTicks*nanosecPerTick)/(1000.0*1000.0));
+Console.WriteLine("EvalQuery took {0} msecs", (evalTicks*nanosecPerTick)/(1000.0*1000.0));
+
+
diff --git a/bindings/csharp/Regorus.cs b/bindings/csharp/Regorus.cs
new file mode 100644
index 00000000..d63670cd
--- /dev/null
+++ b/bindings/csharp/Regorus.cs
@@ -0,0 +1,169 @@
+using System.Text;
+
+namespace Regorus
+{
+    public class Exception : System.Exception
+    {
+	public Exception(string? message) : base(message) {}
+    }
+
+    public class Engine : ICloneable
+    {
+	unsafe private RegorusFFI.RegorusEngine* E;
+	public Engine()
+	{
+	    unsafe
+	    {
+		E = RegorusFFI.API.regorus_engine_new();
+	    }
+	}
+
+	public object Clone()
+	{
+	    var clone = (Engine)this.MemberwiseClone();
+	    unsafe
+	    {
+		clone.E = RegorusFFI.API.regorus_engine_clone(E);
+	    }
+	    return clone;
+
+	}
+
+	public void AddPolicy(string path, string rego)
+	{
+	    var pathBytes = Encoding.UTF8.GetBytes(path);
+	    var regoBytes = Encoding.UTF8.GetBytes(rego);
+	    
+	    unsafe
+	    {
+		fixed (byte* pathPtr = pathBytes)
+		{
+		    fixed(byte* regoPtr = regoBytes)
+		    {
+			CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy(E, pathPtr, regoPtr));
+		    }
+		}		
+	    }
+	}
+
+	public void AddPolicyFromFile(string path)
+	{
+	    var pathBytes = Encoding.UTF8.GetBytes(path);
+	    
+	    unsafe
+	    {
+		fixed (byte* pathPtr = pathBytes)
+		{
+		    CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy_from_file(E, pathPtr));
+		}		
+	    }
+	}
+	
+	public void AddDataJson(string data)
+	{
+	    var dataBytes = Encoding.UTF8.GetBytes(data);
+	    
+	    unsafe
+	    {
+		fixed (byte* dataPtr = dataBytes)
+		{
+		    CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_json(E, dataPtr));
+
+		}		
+	    }
+	}
+	
+	public void AddDataFromJsonFile(string path)
+	{
+	    var pathBytes = Encoding.UTF8.GetBytes(path);
+	    
+	    unsafe
+	    {
+		fixed (byte* pathPtr = pathBytes)
+		{
+		    CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_from_json_file(E, pathPtr));
+
+		}		
+	    }
+	}
+	
+	public void SetInputJson(string input)
+	{
+	    var inputBytes = Encoding.UTF8.GetBytes(input);
+	    
+	    unsafe
+	    {
+		fixed (byte* inputPtr = inputBytes)
+		{
+		    CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_json(E, inputPtr));
+
+		}		
+	    }
+	}
+	
+	public void SetInputFromJsonFile(string path)
+	{
+	    var pathBytes = Encoding.UTF8.GetBytes(path);
+	    
+	    unsafe
+	    {
+		fixed (byte* pathPtr = pathBytes)
+		{
+		    CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_from_json_file(E, pathPtr));
+
+		}		
+	    }
+	}
+	
+	public string EvalQuery(string query)
+	{
+	    var queryBytes = Encoding.UTF8.GetBytes(query);
+
+	    var resultJson = "";
+	    unsafe
+	    {
+		fixed (byte* queryPtr = queryBytes)
+		{
+		    var result = RegorusFFI.API.regorus_engine_eval_query(E, queryPtr);
+		    if (result.status == RegorusFFI.RegorusStatus.RegorusStatusOk) {
+			if (result.output is not null) {
+			    resultJson = System.Runtime.InteropServices.Marshal.PtrToStringUTF8((IntPtr)result.output);
+			}
+			RegorusFFI.API.regorus_result_drop(result);
+		    } else {
+			CheckAndDropResult(result);
+		    }
+		    
+		}		
+	    }
+	    if (resultJson is not null) {
+		return resultJson;
+	    } else {
+		return "";
+	    }
+	}
+	
+	~Engine()
+	{
+	    unsafe
+	    {
+		RegorusFFI.API.regorus_engine_drop(E);
+	    }
+	}
+
+	
+	void CheckAndDropResult(RegorusFFI.RegorusResult result)
+	{
+	    if (result.status != RegorusFFI.RegorusStatus.RegorusStatusOk) {
+		unsafe {
+		    var message = System.Runtime.InteropServices.Marshal.PtrToStringUTF8((IntPtr)result.error_message);
+		    var ex = new Exception(message);
+		    RegorusFFI.API.regorus_result_drop(result);
+		    throw ex;
+		}
+	    }
+	    RegorusFFI.API.regorus_result_drop(result);		    
+	}
+
+    }
+}
diff --git a/bindings/ffi/Cargo.toml b/bindings/ffi/Cargo.toml
new file mode 100644
index 00000000..57e97c03
--- /dev/null
+++ b/bindings/ffi/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "regorus-ffi"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+[lib]
+crate-type = ["cdylib"]
+
+[dependencies]
+anyhow = "1.0.79"
+regorus = { path  = "../.." }
+serde_json = "1.0.113"
+
+[build-dependencies]
+cbindgen = "0.26.0"
+csbindgen = "1.9.0"
diff --git a/bindings/ffi/RegorusFFI.g.cs b/bindings/ffi/RegorusFFI.g.cs
new file mode 100644
index 00000000..3b3b12f1
--- /dev/null
+++ b/bindings/ffi/RegorusFFI.g.cs
@@ -0,0 +1,88 @@
+// <auto-generated>
+// This code is generated by csbindgen.
+// DON'T CHANGE THIS DIRECTLY.
+// </auto-generated>
+#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 = "regorusc";
+
+
+
+        /// <summary>Drop a `RegorusResult`.  `output` and `error_message` strings are not valid after drop.</summary>
+        [DllImport(__DllName, EntryPoint = "regorus_result_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
+        public static extern void regorus_result_drop(RegorusResult r);
+
+        /// <summary>Construct a new Engine  See https://docs.rs/regorus/latest/regorus/struct.Engine.html</summary>
+        [DllImport(__DllName, EntryPoint = "regorus_engine_new", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
+        public static extern RegorusEngine* regorus_engine_new();
+
+        /// <summary>Clone a [`RegorusEngine`]  To avoid having to parse same policy again, the engine can be cloned after policies and data have been added.</summary>
+        [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);
+
+        /// <summary>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.</summary>
+        [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);
+
+        /// <summary>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.</summary>
+        [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);
+
+        /// <summary>Clear policy data.  See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.clear_data</summary>
+        [DllImport(__DllName, EntryPoint = "regorus_engine_clear_data", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
+        public static extern RegorusResult regorus_engine_clear_data(RegorusEngine* engine);
+
+        /// <summary>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.</summary>
+        [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);
+
+        /// <summary>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.</summary>
+        [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,
+    }
+
+
+}
+    
\ No newline at end of file
diff --git a/bindings/ffi/build.rs b/bindings/ffi/build.rs
new file mode 100644
index 00000000..8707ad5d
--- /dev/null
+++ b/bindings/ffi/build.rs
@@ -0,0 +1,32 @@
+extern crate cbindgen;
+extern crate csbindgen;
+
+use std::env;
+
+fn main() {
+    let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
+
+    cbindgen::Builder::new()
+        .with_crate(&crate_dir)
+        .with_language(cbindgen::Language::C)
+        .with_include_guard("REGORUS_H")
+        .generate()
+        .expect("Unable to generate bindings")
+        .write_to_file("regorus.h");
+
+    cbindgen::Builder::new()
+        .with_crate(crate_dir)
+        .with_language(cbindgen::Language::Cxx)
+        .with_include_guard("REGORUS_HPP")
+        .generate()
+        .expect("Unable to generate bindings")
+        .write_to_file("regorus.hpp");
+
+    csbindgen::Builder::default()
+        .input_extern_file("src/lib.rs")
+        .csharp_dll_name("regorusc")
+        .csharp_class_name("API")
+        .csharp_namespace("RegorusFFI")
+        .generate_csharp_file("./RegorusFFI.g.cs")
+        .unwrap();
+}
diff --git a/bindings/ffi/cbindgen.toml b/bindings/ffi/cbindgen.toml
new file mode 100644
index 00000000..6807e21e
--- /dev/null
+++ b/bindings/ffi/cbindgen.toml
@@ -0,0 +1,158 @@
+# This is a template cbindgen.toml file with all of the default values.
+# Some values are commented out because their absence is the real default.
+#
+# See https://github.com/mozilla/cbindgen/blob/master/docs.md#cbindgentoml
+# for detailed documentation of every option here.
+
+
+
+language = "C++"
+
+
+
+############## Options for Wrapping the Contents of the Header #################
+
+# header = "/* Text to put at the beginning of the generated file. Probably a license. */"
+# trailer = "/* Text to put at the end of the generated file */"
+# include_guard = "my_bindings_h"
+# pragma_once = true
+# autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
+include_version = false
+# namespace = "my_namespace"
+namespaces = []
+using_namespaces = []
+sys_includes = []
+includes = []
+no_includes = false
+after_includes = ""
+
+
+
+
+############################ Code Style Options ################################
+
+braces = "SameLine"
+line_length = 100
+tab_width = 2
+documentation = true
+documentation_style = "auto"
+documentation_length = "full"
+line_endings = "LF" # also "CR", "CRLF", "Native"
+
+
+
+
+############################# Codegen Options ##################################
+
+style = "both"
+sort_by = "Name" # default for `fn.sort_by` and `const.sort_by`
+usize_is_size_t = true
+
+
+
+[defines]
+# "target_os = freebsd" = "DEFINE_FREEBSD"
+# "feature = serde" = "DEFINE_SERDE"
+
+
+
+[export]
+include = []
+exclude = []
+# prefix = "CAPI_"
+item_types = []
+renaming_overrides_prefixing = false
+
+
+
+[export.rename]
+
+
+
+[export.body]
+
+
+[export.mangle]
+
+
+[fn]
+rename_args = "None"
+# must_use = "MUST_USE_FUNC"
+# deprecated = "DEPRECATED_FUNC"
+# deprecated_with_note = "DEPRECATED_FUNC_WITH_NOTE"
+# no_return = "NO_RETURN"
+# prefix = "START_FUNC"
+# postfix = "END_FUNC"
+args = "auto"
+sort_by = "Name"
+
+
+
+
+[struct]
+rename_fields = "None"
+# must_use = "MUST_USE_STRUCT"
+# deprecated = "DEPRECATED_STRUCT"
+# deprecated_with_note = "DEPRECATED_STRUCT_WITH_NOTE"
+derive_constructor = false
+derive_eq = false
+derive_neq = false
+derive_lt = false
+derive_lte = false
+derive_gt = false
+derive_gte = false
+
+
+
+
+[enum]
+rename_variants = "None"
+# must_use = "MUST_USE_ENUM"
+# deprecated = "DEPRECATED_ENUM"
+# deprecated_with_note = "DEPRECATED_ENUM_WITH_NOTE"
+add_sentinel = false
+prefix_with_name = false
+derive_helper_methods = false
+derive_const_casts = false
+derive_mut_casts = false
+# cast_assert_name = "ASSERT"
+derive_tagged_enum_destructor = false
+derive_tagged_enum_copy_constructor = false
+enum_class = true
+private_default_tagged_enum_constructor = false
+
+
+
+
+[const]
+allow_static_const = true
+allow_constexpr = false
+sort_by = "Name"
+
+
+
+
+[macro_expansion]
+bitflags = false
+
+
+
+
+
+
+############## Options for How Your Rust library Should Be Parsed ##############
+
+[parse]
+parse_deps = false
+# include = []
+exclude = []
+clean = false
+extra_bindings = []
+
+
+
+[parse.expand]
+crates = []
+all_features = false
+default_features = true
+features = []
\ No newline at end of file
diff --git a/bindings/ffi/regorus.h b/bindings/ffi/regorus.h
new file mode 100644
index 00000000..6f805854
--- /dev/null
+++ b/bindings/ffi/regorus.h
@@ -0,0 +1,127 @@
+#ifndef REGORUS_H
+#define REGORUS_H
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+/**
+ * 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);
+
+#endif /* REGORUS_H */
diff --git a/bindings/ffi/regorus.hpp b/bindings/ffi/regorus.hpp
new file mode 100644
index 00000000..e58b9d0b
--- /dev/null
+++ b/bindings/ffi/regorus.hpp
@@ -0,0 +1,95 @@
+#ifndef REGORUS_HPP
+#define REGORUS_HPP
+
+#include <cstdarg>
+#include <cstdint>
+#include <cstdlib>
+#include <ostream>
+#include <new>
+
+/// 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 "C"
+
+#endif // REGORUS_HPP
diff --git a/bindings/ffi/src/lib.rs b/bindings/ffi/src/lib.rs
new file mode 100644
index 00000000..eb4c8ba0
--- /dev/null
+++ b/bindings/ffi/src/lib.rs
@@ -0,0 +1,248 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+use anyhow::{anyhow, bail, Result};
+use std::ffi::{CStr, CString};
+use std::os::raw::c_char;
+
+/// Status of a call on `RegorusEngine`.
+#[repr(C)]
+pub enum RegorusStatus {
+    /// The operation was successful.
+    RegorusStatusOk,
+
+    /// The operation was unsuccessful.
+    RegorusStatusError,
+}
+
+/// Result of a call on `RegorusEngine`.
+///
+/// Must be freed using `regorus_result_drop`.
+#[repr(C)]
+pub struct RegorusResult {
+    /// Status
+    status: RegorusStatus,
+
+    /// Output produced by the call.
+    /// Owned by Rust.
+    output: *mut c_char,
+
+    /// Errors produced by the call.
+    /// Owned by Rust.
+    error_message: *mut c_char,
+}
+
+fn to_c_str(s: String) -> *mut c_char {
+    match CString::new(s) {
+        Ok(cs) => cs.into_raw(),
+        _ => to_c_str("binding error: failed to create c-style string".to_string()),
+    }
+}
+
+fn from_c_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(|s| s.to_string())
+    }
+}
+
+fn to_ref<T>(t: &*mut T) -> Result<&mut T> {
+    unsafe { t.as_mut().ok_or_else(|| anyhow!("null pointer")) }
+}
+
+fn to_regorus_result(r: Result<()>) -> RegorusResult {
+    match r {
+        Ok(()) => RegorusResult {
+            status: RegorusStatus::RegorusStatusOk,
+            output: std::ptr::null_mut(),
+            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 {
+    engine: ::regorus::Engine,
+}
+
+/// Drop a `RegorusResult`.
+///
+/// `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 {
+            let _ = CString::from_raw(r.error_message);
+        }
+    }
+}
+
+#[no_mangle]
+/// Construct a new Engine
+///
+/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html
+pub extern "C" fn regorus_engine_new() -> *mut RegorusEngine {
+    let engine = ::regorus::Engine::new();
+    Box::into_raw(Box::new(RegorusEngine { engine }))
+}
+
+/// Clone a [`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()))
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn regorus_engine_drop(engine: *mut RegorusEngine) {
+    if !engine.is_null() {
+        unsafe {
+            let _ = Box::from_raw(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.
+
+#[no_mangle]
+pub extern "C" fn regorus_engine_add_policy(
+    engine: *mut RegorusEngine,
+    path: *const c_char,
+    rego: *const c_char,
+) -> RegorusResult {
+    to_regorus_result(|| -> Result<()> {
+        to_ref(&engine)?
+            .engine
+            .add_policy(from_c_str(path)?, from_c_str(rego)?)
+    }())
+}
+
+#[no_mangle]
+pub extern "C" fn regorus_engine_add_policy_from_file(
+    engine: *mut RegorusEngine,
+    path: *const c_char,
+) -> RegorusResult {
+    to_regorus_result(|| -> Result<()> {
+        to_ref(&engine)?
+            .engine
+            .add_policy_from_file(from_c_str(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.
+#[no_mangle]
+pub extern "C" fn regorus_engine_add_data_json(
+    engine: *mut RegorusEngine,
+    data: *const c_char,
+) -> RegorusResult {
+    to_regorus_result(|| -> Result<()> {
+        to_ref(&engine)?
+            .engine
+            .add_data(regorus::Value::from_json_str(&from_c_str(data)?)?)
+    }())
+}
+
+#[no_mangle]
+pub extern "C" fn regorus_engine_add_data_from_json_file(
+    engine: *mut RegorusEngine,
+    path: *const c_char,
+) -> RegorusResult {
+    to_regorus_result(|| -> Result<()> {
+        to_ref(&engine)?
+            .engine
+            .add_data(regorus::Value::from_json_file(&from_c_str(path)?)?)
+    }())
+}
+
+/// Clear policy data.
+///
+/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.clear_data
+#[no_mangle]
+pub extern "C" fn regorus_engine_clear_data(engine: *mut RegorusEngine) -> RegorusResult {
+    to_regorus_result(|| -> Result<()> {
+        to_ref(&engine)?.engine.clear_data();
+        Ok(())
+    }())
+}
+
+/// 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.
+#[no_mangle]
+pub extern "C" fn regorus_engine_set_input_json(
+    engine: *mut RegorusEngine,
+    input: *const c_char,
+) -> RegorusResult {
+    to_regorus_result(|| -> Result<()> {
+        to_ref(&engine)?
+            .engine
+            .set_input(regorus::Value::from_json_str(&from_c_str(input)?)?);
+        Ok(())
+    }())
+}
+
+#[no_mangle]
+pub extern "C" fn regorus_engine_set_input_from_json_file(
+    engine: *mut RegorusEngine,
+    path: *const c_char,
+) -> RegorusResult {
+    to_regorus_result(|| -> Result<()> {
+        to_ref(&engine)?
+            .engine
+            .set_input(regorus::Value::from_json_file(&from_c_str(path)?)?);
+        Ok(())
+    }())
+}
+
+/// 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.
+#[no_mangle]
+pub extern "C" fn regorus_engine_eval_query(
+    engine: *mut RegorusEngine,
+    query: *const c_char,
+) -> RegorusResult {
+    let output = || -> Result<String> {
+        let results = to_ref(&engine)?
+            .engine
+            .eval_query(from_c_str(query)?, false)?;
+        Ok(serde_json::to_string_pretty(&results)?)
+    }();
+    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)),
+    }
+}
diff --git a/bindings/go/go.mod b/bindings/go/go.mod
new file mode 100644
index 00000000..2f0af35c
--- /dev/null
+++ b/bindings/go/go.mod
@@ -0,0 +1,3 @@
+module regorus-test
+
+go 1.21.5
diff --git a/bindings/go/main.go b/bindings/go/main.go
new file mode 100644
index 00000000..d00837ea
--- /dev/null
+++ b/bindings/go/main.go
@@ -0,0 +1,58 @@
+package main
+
+import (
+	"fmt"
+	"os"
+	"regorus-test/pkg/regorus"
+	"time"
+)
+
+func main() {
+	var output string
+	var err error
+
+	t := time.Now();
+	
+	// Create new engine
+	engine := regorus.NewEngine()
+	defer engine.Close()
+	elapsed1 := time.Since(t)
+
+	t = time.Now()
+	// Add policies and data.
+	policies := []string{
+		"../../tests/aci/framework.rego",
+		"../../tests/aci/api.rego",
+		"../../tests/aci/policy.rego",
+	}
+	for _, policy := range policies {
+		if err := engine.AddPolicyFromFile(policy); err != nil {
+			fmt.Fprintf(os.Stderr, "error: %v\n", err)
+			os.Exit(1)
+		}
+	}
+	if err = engine.AddDataFromJsonFile("../../tests/aci/data.json"); err != nil {
+		fmt.Fprintf(os.Stderr, "error: %v\n", err)
+		os.Exit(1)
+	}
+	elapsed2 := time.Since(t)
+
+	t = time.Now()
+	// Set input and eval query.
+	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 {
+		fmt.Fprintf(os.Stderr, "error: %v\n", err)
+		os.Exit(1)
+	}
+	elapsed3 := time.Since(t)
+
+	fmt.Println("{%s}", 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)
+}
diff --git a/bindings/go/pkg/regorus/mod.go b/bindings/go/pkg/regorus/mod.go
new file mode 100644
index 00000000..e1529ef5
--- /dev/null
+++ b/bindings/go/pkg/regorus/mod.go
@@ -0,0 +1,117 @@
+package regorus
+
+// #cgo LDFLAGS: -L ../../../../target/release -lregorus_ffi
+// #include "../../../ffi/regorus.h"
+import "C"
+import (
+	"fmt"
+	"unsafe"
+)
+
+type Engine struct {
+	e *C.RegorusEngine
+}
+
+func NewEngine() *Engine {
+	e := new(Engine)
+	e.e = C.regorus_engine_new()
+	return e
+}
+
+func (e *Engine) Close() {
+	C.regorus_engine_drop(e.e)
+}
+
+func (e *Engine) Clone() *Engine {
+	c := new(Engine)
+	c.e = C.regorus_engine_clone(e.e)
+	return c
+}
+
+func (e *Engine) AddPolicy(path string, rego string) error {
+	path_c := C.CString(path)
+	defer C.free(unsafe.Pointer(path_c))
+
+	rego_c := C.CString(rego)
+	defer C.free(unsafe.Pointer(rego_c))
+
+	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 nil
+}
+
+func (e *Engine) AddPolicyFromFile(path 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 nil
+}
+
+func (e *Engine) AddDataJson(data string) error {
+	data_c := C.CString(data)
+	defer C.free(unsafe.Pointer(data_c))
+
+	result := C.regorus_engine_add_data_json(e.e, data_c)
+	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) AddDataFromJsonFile(path string) error {
+	path_c := C.CString(path)
+	defer C.free(unsafe.Pointer(path_c))
+
+	result := C.regorus_engine_add_data_from_json_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 nil
+}
+
+func (e *Engine) SetInputJson(input string) error {
+	input_c := C.CString(input)
+	defer C.free(unsafe.Pointer(input_c))
+
+	result := C.regorus_engine_set_input_json(e.e, input_c)
+	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) SetInputFromJsonFile(path string) error {
+	path_c := C.CString(path)
+	defer C.free(unsafe.Pointer(path_c))
+
+	result := C.regorus_engine_set_input_from_json_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 nil
+}
+
+func (e *Engine) EvalQuery(query string) (string, error) {
+	query_c := C.CString(query)
+	defer C.free(unsafe.Pointer(query_c))
+
+	result := C.regorus_engine_eval_query(e.e, query_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
+}
diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs
index 68539aa4..08435f57 100644
--- a/bindings/wasm/src/lib.rs
+++ b/bindings/wasm/src/lib.rs
@@ -64,6 +64,8 @@ impl Engine {
     }
 
     /// 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> {
         self.engine.clear_data();
         Ok(())


Test patch:
diff --git a/bindings/csharp/regorus-test.csproj b/bindings/csharp/regorus-test.csproj
new file mode 100644
index 00000000..ddf818d6
--- /dev/null
+++ b/bindings/csharp/regorus-test.csproj
@@ -0,0 +1,24 @@
+﻿<Project Sdk="Microsoft.NET.Sdk"> 
+
+  <Target Name="BuildRegorusFFI">
+    <Exec Command="cargo build -r --manifest-path ../ffi/Cargo.toml" />
+    <Copy SourceFiles="../ffi/RegorusFFI.g.cs" DestinationFolder="." />
+    <ItemGroup>
+        <RegorusDylib Include="..\..\target\release\*regorus_ffi*" />
+    </ItemGroup>    
+    <Copy SourceFiles="@(RegorusDylib)" DestinationFolder="." />
+  </Target>
+
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net8.0</TargetFramework>
+    <RootNamespace>regorus_test</RootNamespace>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+  </PropertyGroup>
+
+
+
+</Project>