mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
feat(bindings)!: add RVM/Program support across FFI and language bindings (#565)
- FFI: add RVM/Program APIs, execution state accessors, HostAwait handling, and buffer/result helpers in rvm.rs, common.rs, engine.rs. - Compiler: emit HostAwait for __builtin_host_await in function_calls.rs. - RVM tests: add HostAwait regression cases and extend harness for suspend/resume responses in host_await.yaml and mod.rs. - C/C++: add RVM tests/examples and wrapper updates in rvm_tests.c, rvm_tests.cpp, regorus.hpp, plus CMake wiring. - C#: add Program/Rvm bindings, SafeHandle/PInvoke, tests, and example usage in Regorus, RvmProgramTests.cs, Program.cs, and README updates. - Go: add Program/Rvm bindings, tests, and examples in rvm.go, rvm_test.go, main.go. - Java: add Program/Rvm bindings, JNI glue, and examples in lib.rs, regorus, Test.java. - Python: add Program/Rvm bindings and examples in lib.rs, test.py. - WASM: add Program/Rvm bindings and examples in lib.rs, test.js. - Tooling: wire binding tests in xtask and ignore generated Java artifacts in .gitignore. Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
committed by
GitHub
parent
0316ccd90c
commit
3f7a5496dc
@@ -14,6 +14,7 @@ FetchContent_MakeAvailable(Corrosion)
|
||||
|
||||
project("regorus-test")
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
enable_testing()
|
||||
|
||||
# installable ffi target
|
||||
|
||||
@@ -83,3 +84,9 @@ install(FILES
|
||||
|
||||
add_executable(regorus_test main.cpp)
|
||||
target_link_libraries(regorus_test regorus_ffi::regorus_ffi)
|
||||
|
||||
add_executable(regorus_rvm_test rvm_tests.cpp)
|
||||
target_link_libraries(regorus_rvm_test regorus_ffi::regorus_ffi)
|
||||
|
||||
add_test(NAME regorus_cpp_engine COMMAND regorus_test)
|
||||
add_test(NAME regorus_cpp_rvm COMMAND regorus_rvm_test)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#ifndef REGORUS_WRAPPER_HPP
|
||||
#define REGORUS_WRAPPER_HPP
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <variant>
|
||||
|
||||
@@ -8,8 +10,11 @@
|
||||
|
||||
namespace regorus {
|
||||
|
||||
class Result {
|
||||
public:
|
||||
class Buffer;
|
||||
class Program;
|
||||
|
||||
class Result {
|
||||
public:
|
||||
|
||||
operator bool() const { return result.status == RegorusStatus::Ok; }
|
||||
bool operator !() const { return result.status != RegorusStatus::Ok; }
|
||||
@@ -30,18 +35,39 @@ namespace regorus {
|
||||
}
|
||||
}
|
||||
|
||||
void* pointer() const {
|
||||
return result.pointer_value;
|
||||
}
|
||||
|
||||
Program program() const;
|
||||
Buffer buffer() const;
|
||||
|
||||
Result(RegorusResult r) : result(r) {}
|
||||
Result(Result&& other) noexcept : result(other.result) {
|
||||
other.result.output = nullptr;
|
||||
other.result.error_message = nullptr;
|
||||
other.result.pointer_value = nullptr;
|
||||
}
|
||||
Result& operator=(Result&& other) noexcept {
|
||||
if (this != &other) {
|
||||
regorus_result_drop(result);
|
||||
result = other.result;
|
||||
other.result.output = nullptr;
|
||||
other.result.error_message = nullptr;
|
||||
other.result.pointer_value = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
~Result() {
|
||||
regorus_result_drop(result);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class Engine;
|
||||
RegorusResult result;
|
||||
|
||||
Result(RegorusResult r) : result(r) {}
|
||||
private:
|
||||
Result(const Result&) = delete;
|
||||
Result(Result&&) = delete;
|
||||
Result& operator=(const Result&) = delete;
|
||||
|
||||
};
|
||||
@@ -109,6 +135,10 @@ namespace regorus {
|
||||
~Engine() {
|
||||
regorus_engine_drop(engine);
|
||||
}
|
||||
|
||||
RegorusEngine* raw() const {
|
||||
return engine;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
@@ -119,6 +149,247 @@ namespace regorus {
|
||||
Engine(Engine&&) = delete;
|
||||
Engine& operator=(const Engine&) = delete;
|
||||
};
|
||||
|
||||
class CompiledPolicy {
|
||||
public:
|
||||
explicit CompiledPolicy(RegorusCompiledPolicy* p) : policy(p) {}
|
||||
|
||||
Result eval_with_input(const char* input_json) {
|
||||
return Result(regorus_compiled_policy_eval_with_input(policy, input_json));
|
||||
}
|
||||
|
||||
Result get_policy_info() {
|
||||
return Result(regorus_compiled_policy_get_policy_info(policy));
|
||||
}
|
||||
|
||||
RegorusCompiledPolicy* raw() const {
|
||||
return policy;
|
||||
}
|
||||
|
||||
~CompiledPolicy() {
|
||||
if (policy) {
|
||||
regorus_compiled_policy_drop(policy);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
RegorusCompiledPolicy* policy;
|
||||
CompiledPolicy(const CompiledPolicy&) = delete;
|
||||
CompiledPolicy(CompiledPolicy&&) = delete;
|
||||
CompiledPolicy& operator=(const CompiledPolicy&) = delete;
|
||||
};
|
||||
|
||||
class Buffer {
|
||||
public:
|
||||
Buffer() : buffer(nullptr) {}
|
||||
explicit Buffer(RegorusBuffer* b) : buffer(b) {}
|
||||
|
||||
const std::uint8_t* data() const {
|
||||
return buffer ? buffer->data : nullptr;
|
||||
}
|
||||
|
||||
size_t size() const {
|
||||
return buffer ? buffer->len : 0;
|
||||
}
|
||||
|
||||
RegorusBuffer* raw() const {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
~Buffer() {
|
||||
if (buffer) {
|
||||
regorus_buffer_drop(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
RegorusBuffer* buffer;
|
||||
Buffer(const Buffer&) = delete;
|
||||
Buffer(Buffer&&) = delete;
|
||||
Buffer& operator=(const Buffer&) = delete;
|
||||
};
|
||||
|
||||
class Program {
|
||||
public:
|
||||
Program() : program(regorus_program_new()) {}
|
||||
explicit Program(RegorusProgram* p) : program(p) {}
|
||||
|
||||
static Result compile_from_policy(
|
||||
RegorusCompiledPolicy* compiled_policy,
|
||||
const char* const* entry_points,
|
||||
size_t entry_points_len
|
||||
) {
|
||||
return Result(regorus_program_compile_from_policy(
|
||||
compiled_policy,
|
||||
entry_points,
|
||||
entry_points_len
|
||||
));
|
||||
}
|
||||
|
||||
static Result compile_from_modules(
|
||||
const char* data_json,
|
||||
const RegorusPolicyModule* modules,
|
||||
size_t modules_len,
|
||||
const char* const* entry_points,
|
||||
size_t entry_points_len
|
||||
) {
|
||||
return Result(regorus_program_compile_from_modules(
|
||||
data_json,
|
||||
modules,
|
||||
modules_len,
|
||||
entry_points,
|
||||
entry_points_len
|
||||
));
|
||||
}
|
||||
|
||||
static Result compile_from_engine(
|
||||
RegorusEngine* engine,
|
||||
const char* const* entry_points,
|
||||
size_t entry_points_len
|
||||
) {
|
||||
return Result(regorus_engine_compile_program_with_entrypoints(
|
||||
engine,
|
||||
entry_points,
|
||||
entry_points_len
|
||||
));
|
||||
}
|
||||
|
||||
Result serialize_binary() const {
|
||||
return Result(regorus_program_serialize_binary(program));
|
||||
}
|
||||
|
||||
static Result deserialize_binary(
|
||||
const std::uint8_t* data,
|
||||
size_t len,
|
||||
bool* is_partial
|
||||
) {
|
||||
return Result(regorus_program_deserialize_binary(data, len, is_partial));
|
||||
}
|
||||
|
||||
Result generate_listing() const {
|
||||
return Result(regorus_program_generate_listing(program));
|
||||
}
|
||||
|
||||
Result generate_tabular_listing() const {
|
||||
return Result(regorus_program_generate_tabular_listing(program));
|
||||
}
|
||||
|
||||
RegorusProgram* raw() const {
|
||||
return program;
|
||||
}
|
||||
|
||||
~Program() {
|
||||
if (program) {
|
||||
regorus_program_drop(program);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
RegorusProgram* program;
|
||||
Program(const Program&) = delete;
|
||||
Program(Program&&) = delete;
|
||||
Program& operator=(const Program&) = delete;
|
||||
};
|
||||
|
||||
inline Program Result::program() const {
|
||||
return Program(reinterpret_cast<RegorusProgram*>(result.pointer_value));
|
||||
}
|
||||
|
||||
inline Buffer Result::buffer() const {
|
||||
return Buffer(reinterpret_cast<RegorusBuffer*>(result.pointer_value));
|
||||
}
|
||||
|
||||
class Rvm {
|
||||
public:
|
||||
Rvm() : vm(regorus_rvm_new()) {}
|
||||
explicit Rvm(RegorusRvm* v) : vm(v) {}
|
||||
|
||||
static Result create_with_policy(RegorusCompiledPolicy* compiled_policy) {
|
||||
return Result(regorus_rvm_new_with_policy(compiled_policy));
|
||||
}
|
||||
|
||||
Result load_program(const Program& program) {
|
||||
return Result(regorus_rvm_load_program(vm, program.raw()));
|
||||
}
|
||||
|
||||
Result set_data(const char* data_json) {
|
||||
return Result(regorus_rvm_set_data(vm, data_json));
|
||||
}
|
||||
|
||||
Result set_input(const char* input_json) {
|
||||
return Result(regorus_rvm_set_input(vm, input_json));
|
||||
}
|
||||
|
||||
Result set_max_instructions(size_t max_instructions) {
|
||||
return Result(regorus_rvm_set_max_instructions(vm, max_instructions));
|
||||
}
|
||||
|
||||
Result set_strict_builtin_errors(bool strict) {
|
||||
return Result(regorus_rvm_set_strict_builtin_errors(vm, strict));
|
||||
}
|
||||
|
||||
Result set_execution_mode(std::uint8_t mode) {
|
||||
return Result(regorus_rvm_set_execution_mode(vm, mode));
|
||||
}
|
||||
|
||||
Result set_step_mode(bool enabled) {
|
||||
return Result(regorus_rvm_set_step_mode(vm, enabled));
|
||||
}
|
||||
|
||||
Result set_execution_timer_config(bool has_config, RegorusExecutionTimerConfig config) {
|
||||
return Result(regorus_rvm_set_execution_timer_config(vm, has_config, config));
|
||||
}
|
||||
|
||||
Result execute() {
|
||||
return Result(regorus_rvm_execute(vm));
|
||||
}
|
||||
|
||||
Result execute_entry_point_by_name(const char* entry_point) {
|
||||
return Result(regorus_rvm_execute_entry_point_by_name(vm, entry_point));
|
||||
}
|
||||
|
||||
Result execute_entry_point_by_index(size_t index) {
|
||||
return Result(regorus_rvm_execute_entry_point_by_index(vm, index));
|
||||
}
|
||||
|
||||
Result resume(const char* resume_value_json, bool has_value) {
|
||||
return Result(regorus_rvm_resume(vm, resume_value_json, has_value));
|
||||
}
|
||||
|
||||
Result get_execution_state() {
|
||||
return Result(regorus_rvm_get_execution_state(vm));
|
||||
}
|
||||
|
||||
RegorusRvm* raw() const {
|
||||
return vm;
|
||||
}
|
||||
|
||||
~Rvm() {
|
||||
if (vm) {
|
||||
regorus_rvm_drop(vm);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
RegorusRvm* vm;
|
||||
Rvm(const Rvm&) = delete;
|
||||
Rvm(Rvm&&) = delete;
|
||||
Rvm& operator=(const Rvm&) = delete;
|
||||
};
|
||||
|
||||
inline Result compile_policy_with_entrypoint(
|
||||
const char* data_json,
|
||||
const RegorusPolicyModule* modules,
|
||||
size_t modules_len,
|
||||
const char* entry_point
|
||||
) {
|
||||
return Result(regorus_compile_policy_with_entrypoint(
|
||||
data_json,
|
||||
modules,
|
||||
modules_len,
|
||||
entry_point
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#endif // REGORUS_WRAPPER_HPP
|
||||
|
||||
261
bindings/cpp/rvm_tests.cpp
Normal file
261
bindings/cpp/rvm_tests.cpp
Normal file
@@ -0,0 +1,261 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include "regorus.hpp"
|
||||
|
||||
int main() {
|
||||
const char* data_json =
|
||||
"{"
|
||||
" \"roles\": {"
|
||||
" \"alice\": [\"admin\", \"reader\"]"
|
||||
" }"
|
||||
"}";
|
||||
const char* input_json =
|
||||
"{"
|
||||
" \"user\": \"alice\","
|
||||
" \"actions\": [\"read\"]"
|
||||
"}";
|
||||
const char* module_text =
|
||||
"package demo\n"
|
||||
"default allow = false\n"
|
||||
"allow if {\n"
|
||||
" input.user == \"alice\"\n"
|
||||
" some role in data.roles[input.user]\n"
|
||||
" role == \"admin\"\n"
|
||||
" count(input.actions) > 0\n"
|
||||
"}\n";
|
||||
|
||||
const char* host_data_json = "{}";
|
||||
const char* host_input_json = "{\"account\":{\"id\":\"acct-1\",\"active\":true}}";
|
||||
const char* host_module_text =
|
||||
"package demo\n"
|
||||
"import rego.v1\n"
|
||||
"default allow := false\n"
|
||||
"allow if {\n"
|
||||
" input.account.active == true\n"
|
||||
" details := __builtin_host_await(input.account.id, \"account\")\n"
|
||||
" details.tier == \"gold\"\n"
|
||||
"}\n";
|
||||
|
||||
RegorusPolicyModule module;
|
||||
module.id = "demo.rego";
|
||||
module.content = module_text;
|
||||
|
||||
const char* entry_points[] = {"data.demo.allow"};
|
||||
std::cout << "Rego policy:\n" << module_text << std::endl;
|
||||
std::cout << "Compiling program from modules..." << std::endl;
|
||||
auto program_result = regorus::Program::compile_from_modules(
|
||||
data_json,
|
||||
&module,
|
||||
1,
|
||||
entry_points,
|
||||
1
|
||||
);
|
||||
if (!program_result) {
|
||||
std::cerr << "compile program (modules): " << program_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
regorus::Program program = program_result.program();
|
||||
|
||||
std::cout << "Generating assembly listing..." << std::endl;
|
||||
auto listing_result = program.generate_listing();
|
||||
if (!listing_result) {
|
||||
std::cerr << "generate listing: " << listing_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::cout << "Assembly listing:\n" << listing_result.output() << std::endl;
|
||||
|
||||
std::cout << "Serializing program..." << std::endl;
|
||||
auto serialize_result = program.serialize_binary();
|
||||
if (!serialize_result) {
|
||||
std::cerr << "serialize program: " << serialize_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
regorus::Buffer buffer(reinterpret_cast<RegorusBuffer*>(serialize_result.pointer()));
|
||||
bool is_partial = false;
|
||||
std::cout << "Deserializing program (" << buffer.size() << " bytes)..." << std::endl;
|
||||
auto deserialize_result = regorus::Program::deserialize_binary(
|
||||
buffer.data(),
|
||||
buffer.size(),
|
||||
&is_partial
|
||||
);
|
||||
if (!deserialize_result) {
|
||||
std::cerr << "deserialize program: " << deserialize_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (is_partial) {
|
||||
std::cerr << "deserialized program marked partial" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
regorus::Program program2 = deserialize_result.program();
|
||||
|
||||
{
|
||||
std::cout << "Creating VM..." << std::endl;
|
||||
regorus::Rvm vm;
|
||||
auto load_result = vm.load_program(program2);
|
||||
if (!load_result) {
|
||||
std::cerr << "load program: " << load_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "Setting data..." << std::endl;
|
||||
auto data_result = vm.set_data(data_json);
|
||||
if (!data_result) {
|
||||
std::cerr << "set data: " << data_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "Setting input..." << std::endl;
|
||||
auto input_result = vm.set_input(input_json);
|
||||
if (!input_result) {
|
||||
std::cerr << "set input: " << input_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "Executing entry point..." << std::endl;
|
||||
auto exec_result = vm.execute();
|
||||
if (!exec_result) {
|
||||
std::cerr << "execute: " << exec_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "Execution result (data.demo.allow): " << exec_result.output() << std::endl;
|
||||
std::cout << "Decision: user=alice action=read -> allow=" << exec_result.output() << std::endl;
|
||||
if (std::string(exec_result.output()) != "true") {
|
||||
std::cerr << "unexpected result: " << exec_result.output() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
regorus::Engine engine;
|
||||
std::cout << "Compiling program from engine..." << std::endl;
|
||||
auto add_policy_result = engine.add_policy("demo.rego", module_text);
|
||||
if (!add_policy_result) {
|
||||
std::cerr << "engine add policy: " << add_policy_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto engine_program_result = regorus::Program::compile_from_engine(
|
||||
engine.raw(),
|
||||
entry_points,
|
||||
1
|
||||
);
|
||||
if (!engine_program_result) {
|
||||
std::cerr << "compile program (engine): " << engine_program_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
regorus::Program engine_program = engine_program_result.program();
|
||||
|
||||
regorus::Rvm engine_vm;
|
||||
auto engine_load_result = engine_vm.load_program(engine_program);
|
||||
if (!engine_load_result) {
|
||||
std::cerr << "engine load program: " << engine_load_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "Setting engine data..." << std::endl;
|
||||
auto engine_data_result = engine_vm.set_data(data_json);
|
||||
if (!engine_data_result) {
|
||||
std::cerr << "engine set data: " << engine_data_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "Setting engine input..." << std::endl;
|
||||
auto engine_input_result = engine_vm.set_input(input_json);
|
||||
if (!engine_input_result) {
|
||||
std::cerr << "engine set input: " << engine_input_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "Executing engine entry point..." << std::endl;
|
||||
auto engine_exec_result = engine_vm.execute();
|
||||
if (!engine_exec_result) {
|
||||
std::cerr << "engine execute: " << engine_exec_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "Engine execution result (data.demo.allow): " << engine_exec_result.output() << std::endl;
|
||||
std::cout << "Decision: user=alice action=read -> allow=" << engine_exec_result.output() << std::endl;
|
||||
if (std::string(engine_exec_result.output()) != "true") {
|
||||
std::cerr << "unexpected engine result: " << engine_exec_result.output() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "\n--- HostAwait example (suspendable execution) ---" << std::endl;
|
||||
RegorusPolicyModule host_module;
|
||||
host_module.id = "host_await.rego";
|
||||
host_module.content = host_module_text;
|
||||
const char* host_entry_points[] = {"data.demo.allow"};
|
||||
|
||||
auto host_program_result = regorus::Program::compile_from_modules(
|
||||
host_data_json,
|
||||
&host_module,
|
||||
1,
|
||||
host_entry_points,
|
||||
1
|
||||
);
|
||||
if (!host_program_result) {
|
||||
std::cerr << "compile host await program: " << host_program_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
regorus::Program host_program = host_program_result.program();
|
||||
regorus::Rvm host_vm;
|
||||
auto host_mode_result = host_vm.set_execution_mode(1);
|
||||
if (!host_mode_result) {
|
||||
std::cerr << "set execution mode: " << host_mode_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto host_load_result = host_vm.load_program(host_program);
|
||||
if (!host_load_result) {
|
||||
std::cerr << "load host await program: " << host_load_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto host_data_result = host_vm.set_data(host_data_json);
|
||||
if (!host_data_result) {
|
||||
std::cerr << "set host data: " << host_data_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto host_input_result = host_vm.set_input(host_input_json);
|
||||
if (!host_input_result) {
|
||||
std::cerr << "set host input: " << host_input_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto host_exec_result = host_vm.execute();
|
||||
if (!host_exec_result) {
|
||||
std::cerr << "execute host await: " << host_exec_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::cout << "HostAwait initial result: " << host_exec_result.output() << std::endl;
|
||||
|
||||
auto host_state_result = host_vm.get_execution_state();
|
||||
if (!host_state_result) {
|
||||
std::cerr << "get execution state: " << host_state_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::cout << "Execution state: " << host_state_result.output() << std::endl;
|
||||
|
||||
auto host_resume_result = host_vm.resume("{\"tier\":\"gold\"}", true);
|
||||
if (!host_resume_result) {
|
||||
std::cerr << "resume host await: " << host_resume_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::cout << "HostAwait resumed result: " << host_resume_result.output() << std::endl;
|
||||
if (std::string(host_resume_result.output()) != "true") {
|
||||
std::cerr << "unexpected host await result: " << host_resume_result.output() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user