Bindings for C, C#, Golang (#124)

* FFI bindings

Generate C FFI as well as C# FFI

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* Regorus C binding

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* C# binding

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* Golang binding

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

---------

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
Anand Krishnamoorthi
2024-02-04 19:50:44 -08:00
committed by GitHub
parent 25b1ffe6d7
commit 22260ac46f
17 changed files with 1272 additions and 0 deletions

View File

@@ -1,6 +1,7 @@
[workspace]
members = [
"bindings/ffi",
"bindings/python",
"bindings/wasm"
]

27
bindings/c/CMakeLists.txt Normal file
View File

@@ -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)

55
bindings/c/main.c Normal file
View File

@@ -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;
}

View File

@@ -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));

169
bindings/csharp/Regorus.cs Normal file
View File

@@ -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);
}
}
}

View File

@@ -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>

17
bindings/ffi/Cargo.toml Normal file
View File

@@ -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"

View File

@@ -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,
}
}

32
bindings/ffi/build.rs Normal file
View File

@@ -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();
}

158
bindings/ffi/cbindgen.toml Normal file
View File

@@ -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 = []

127
bindings/ffi/regorus.h Normal file
View File

@@ -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 */

95
bindings/ffi/regorus.hpp Normal file
View File

@@ -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

248
bindings/ffi/src/lib.rs Normal file
View File

@@ -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)),
}
}

3
bindings/go/go.mod Normal file
View File

@@ -0,0 +1,3 @@
module regorus-test
go 1.21.5

58
bindings/go/main.go Normal file
View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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(())