diff --git a/Cargo.toml b/Cargo.toml index 92c7b6c..0f4e928 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 0000000..df3a49d --- /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 /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 /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 0000000..5319798 --- /dev/null +++ b/bindings/c/main.c @@ -0,0 +1,55 @@ +#include +#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 0000000..702ecaa --- /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 0000000..d63670c --- /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/csharp/regorus-test.csproj b/bindings/csharp/regorus-test.csproj new file mode 100644 index 0000000..ddf818d --- /dev/null +++ b/bindings/csharp/regorus-test.csproj @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + Exe + net8.0 + regorus_test + enable + enable + true + + + + + diff --git a/bindings/ffi/Cargo.toml b/bindings/ffi/Cargo.toml new file mode 100644 index 0000000..57e97c0 --- /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 0000000..3b3b12f --- /dev/null +++ b/bindings/ffi/RegorusFFI.g.cs @@ -0,0 +1,88 @@ +// +// 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 = "regorusc"; + + + + /// 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, + } + + +} + \ No newline at end of file diff --git a/bindings/ffi/build.rs b/bindings/ffi/build.rs new file mode 100644 index 0000000..8707ad5 --- /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 0000000..6807e21 --- /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 0000000..6f80585 --- /dev/null +++ b/bindings/ffi/regorus.h @@ -0,0 +1,127 @@ +#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); + +#endif /* REGORUS_H */ diff --git a/bindings/ffi/regorus.hpp b/bindings/ffi/regorus.hpp new file mode 100644 index 0000000..e58b9d0 --- /dev/null +++ b/bindings/ffi/regorus.hpp @@ -0,0 +1,95 @@ +#ifndef REGORUS_HPP +#define REGORUS_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 "C" + +#endif // REGORUS_HPP diff --git a/bindings/ffi/src/lib.rs b/bindings/ffi/src/lib.rs new file mode 100644 index 0000000..eb4c8ba --- /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 { + 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: &*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 { + 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 0000000..2f0af35 --- /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 0000000..d00837e --- /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 0000000..e1529ef --- /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 68539aa..08435f5 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(())