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
@@ -13,6 +13,7 @@ FetchContent_Declare(
|
||||
FetchContent_MakeAvailable(Corrosion)
|
||||
|
||||
project("regorus-test")
|
||||
enable_testing()
|
||||
|
||||
corrosion_import_crate(
|
||||
# Path to <regorus-source-folder>/bindings/ffi/Cargo.toml
|
||||
@@ -35,3 +36,10 @@ 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)
|
||||
|
||||
add_executable(regorus_rvm_test rvm_tests.c)
|
||||
target_include_directories(regorus_rvm_test PRIVATE "../ffi")
|
||||
target_link_libraries(regorus_rvm_test regorus_ffi)
|
||||
|
||||
add_test(NAME regorus_c_engine COMMAND regorus_test)
|
||||
add_test(NAME regorus_c_rvm COMMAND regorus_rvm_test)
|
||||
|
||||
289
bindings/c/rvm_tests.c
Normal file
289
bindings/c/rvm_tests.c
Normal file
@@ -0,0 +1,289 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "regorus.h"
|
||||
|
||||
static int assert_ok(RegorusResult r, const char* message) {
|
||||
if (r.status != Ok) {
|
||||
fprintf(stderr, "%s: %s\n", message, r.error_message ? r.error_message : "(no error)");
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main() {
|
||||
RegorusResult result = {0};
|
||||
bool result_valid = false;
|
||||
RegorusProgram* program = NULL;
|
||||
RegorusBuffer* buffer = NULL;
|
||||
RegorusProgram* program2 = NULL;
|
||||
RegorusRvm* vm = NULL;
|
||||
RegorusProgram* host_program = NULL;
|
||||
RegorusRvm* host_vm = NULL;
|
||||
bool is_partial = false;
|
||||
int exit_code = 1;
|
||||
|
||||
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"};
|
||||
printf("Rego policy:\n%s\n", module_text);
|
||||
printf("Compiling program from modules...\n");
|
||||
result = regorus_program_compile_from_modules(
|
||||
data_json,
|
||||
&module,
|
||||
1,
|
||||
entry_points,
|
||||
1
|
||||
);
|
||||
result_valid = true;
|
||||
if (!assert_ok(result, "compile program")) {
|
||||
goto Cleanup;
|
||||
}
|
||||
program = (RegorusProgram*)result.pointer_value;
|
||||
regorus_result_drop(result);
|
||||
result_valid = false;
|
||||
|
||||
printf("Generating assembly listing...\n");
|
||||
result = regorus_program_generate_listing(program);
|
||||
result_valid = true;
|
||||
if (!assert_ok(result, "generate listing")) {
|
||||
goto Cleanup;
|
||||
}
|
||||
printf("Assembly listing:\n%s\n", result.output ? result.output : "(null)");
|
||||
regorus_result_drop(result);
|
||||
result_valid = false;
|
||||
|
||||
printf("Serializing program...\n");
|
||||
result = regorus_program_serialize_binary(program);
|
||||
result_valid = true;
|
||||
if (!assert_ok(result, "serialize program")) {
|
||||
goto Cleanup;
|
||||
}
|
||||
buffer = (RegorusBuffer*)result.pointer_value;
|
||||
regorus_result_drop(result);
|
||||
result_valid = false;
|
||||
|
||||
printf("Deserializing program (%zu bytes)...\n", buffer->len);
|
||||
result = regorus_program_deserialize_binary(
|
||||
buffer->data,
|
||||
buffer->len,
|
||||
&is_partial
|
||||
);
|
||||
result_valid = true;
|
||||
if (!assert_ok(result, "deserialize program")) {
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
if (is_partial) {
|
||||
fprintf(stderr, "deserialized program marked partial\n");
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
program2 = (RegorusProgram*)result.pointer_value;
|
||||
regorus_result_drop(result);
|
||||
result_valid = false;
|
||||
|
||||
printf("Creating VM...\n");
|
||||
vm = regorus_rvm_new();
|
||||
if (!vm) {
|
||||
fprintf(stderr, "failed to allocate VM\n");
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
printf("Loading program into VM...\n");
|
||||
result = regorus_rvm_load_program(vm, program2);
|
||||
result_valid = true;
|
||||
if (!assert_ok(result, "load program")) {
|
||||
goto Cleanup;
|
||||
}
|
||||
regorus_result_drop(result);
|
||||
result_valid = false;
|
||||
|
||||
printf("Setting data...\n");
|
||||
result = regorus_rvm_set_data(vm, data_json);
|
||||
result_valid = true;
|
||||
if (!assert_ok(result, "set data")) {
|
||||
goto Cleanup;
|
||||
}
|
||||
regorus_result_drop(result);
|
||||
result_valid = false;
|
||||
|
||||
printf("Setting input...\n");
|
||||
result = regorus_rvm_set_input(vm, input_json);
|
||||
result_valid = true;
|
||||
if (!assert_ok(result, "set input")) {
|
||||
goto Cleanup;
|
||||
}
|
||||
regorus_result_drop(result);
|
||||
result_valid = false;
|
||||
|
||||
printf("Executing entry point...\n");
|
||||
result = regorus_rvm_execute(vm);
|
||||
result_valid = true;
|
||||
if (!assert_ok(result, "execute")) {
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
printf("Execution result (data.demo.allow): %s\n",
|
||||
result.output ? result.output : "(null)");
|
||||
printf("Decision: user=alice action=read -> allow=%s\n",
|
||||
result.output ? result.output : "(null)");
|
||||
if (!result.output || strcmp(result.output, "true") != 0) {
|
||||
fprintf(stderr, "unexpected result: %s\n", result.output);
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
printf("\n--- HostAwait example (suspendable execution) ---\n");
|
||||
RegorusPolicyModule host_module;
|
||||
host_module.id = "host_await.rego";
|
||||
host_module.content = host_module_text;
|
||||
|
||||
const char* host_entry_points[] = {"data.demo.allow"};
|
||||
result = regorus_program_compile_from_modules(
|
||||
host_data_json,
|
||||
&host_module,
|
||||
1,
|
||||
host_entry_points,
|
||||
1
|
||||
);
|
||||
result_valid = true;
|
||||
if (!assert_ok(result, "compile host await program")) {
|
||||
goto Cleanup;
|
||||
}
|
||||
host_program = (RegorusProgram*)result.pointer_value;
|
||||
regorus_result_drop(result);
|
||||
result_valid = false;
|
||||
|
||||
host_vm = regorus_rvm_new();
|
||||
if (!host_vm) {
|
||||
fprintf(stderr, "failed to allocate host await VM\n");
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
result = regorus_rvm_set_execution_mode(host_vm, 1);
|
||||
result_valid = true;
|
||||
if (!assert_ok(result, "set execution mode")) {
|
||||
goto Cleanup;
|
||||
}
|
||||
regorus_result_drop(result);
|
||||
result_valid = false;
|
||||
|
||||
result = regorus_rvm_load_program(host_vm, host_program);
|
||||
result_valid = true;
|
||||
if (!assert_ok(result, "load host await program")) {
|
||||
goto Cleanup;
|
||||
}
|
||||
regorus_result_drop(result);
|
||||
result_valid = false;
|
||||
|
||||
result = regorus_rvm_set_data(host_vm, host_data_json);
|
||||
result_valid = true;
|
||||
if (!assert_ok(result, "set host data")) {
|
||||
goto Cleanup;
|
||||
}
|
||||
regorus_result_drop(result);
|
||||
result_valid = false;
|
||||
|
||||
result = regorus_rvm_set_input(host_vm, host_input_json);
|
||||
result_valid = true;
|
||||
if (!assert_ok(result, "set host input")) {
|
||||
goto Cleanup;
|
||||
}
|
||||
regorus_result_drop(result);
|
||||
result_valid = false;
|
||||
|
||||
result = regorus_rvm_execute(host_vm);
|
||||
result_valid = true;
|
||||
if (!assert_ok(result, "execute host await")) {
|
||||
goto Cleanup;
|
||||
}
|
||||
printf("HostAwait initial result: %s\n", result.output ? result.output : "(null)");
|
||||
regorus_result_drop(result);
|
||||
result_valid = false;
|
||||
|
||||
result = regorus_rvm_get_execution_state(host_vm);
|
||||
result_valid = true;
|
||||
if (!assert_ok(result, "get execution state")) {
|
||||
goto Cleanup;
|
||||
}
|
||||
printf("Execution state: %s\n", result.output ? result.output : "(null)");
|
||||
regorus_result_drop(result);
|
||||
result_valid = false;
|
||||
|
||||
result = regorus_rvm_resume(host_vm, "{\"tier\":\"gold\"}", true);
|
||||
result_valid = true;
|
||||
if (!assert_ok(result, "resume host await")) {
|
||||
goto Cleanup;
|
||||
}
|
||||
printf("HostAwait resumed result: %s\n", result.output ? result.output : "(null)");
|
||||
|
||||
if (!result.output || strcmp(result.output, "true") != 0) {
|
||||
fprintf(stderr, "unexpected host await result\n");
|
||||
goto Cleanup;
|
||||
}
|
||||
regorus_result_drop(result);
|
||||
result_valid = false;
|
||||
|
||||
exit_code = 0;
|
||||
|
||||
Cleanup:
|
||||
if (result_valid) {
|
||||
regorus_result_drop(result);
|
||||
}
|
||||
if (host_vm) {
|
||||
regorus_rvm_drop(host_vm);
|
||||
}
|
||||
if (host_program) {
|
||||
regorus_program_drop(host_program);
|
||||
}
|
||||
if (vm) {
|
||||
regorus_rvm_drop(vm);
|
||||
}
|
||||
if (program2) {
|
||||
regorus_program_drop(program2);
|
||||
}
|
||||
if (buffer) {
|
||||
regorus_buffer_drop(buffer);
|
||||
}
|
||||
if (program) {
|
||||
regorus_program_drop(program);
|
||||
}
|
||||
return exit_code;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -64,3 +64,43 @@ Regorus.MemoryLimits.SetThreadFlushThresholdOverride(null);
|
||||
```
|
||||
|
||||
See bindings/csharp/Regorus.Tests/RegorusTests.cs for scenario coverage and bindings/csharp/TargetExampleApp/Program.cs for end-to-end usage.
|
||||
|
||||
## RVM Usage Example
|
||||
|
||||
The RVM API lets you compile a program from modules/entrypoints and execute it in a VM:
|
||||
|
||||
```csharp
|
||||
using Regorus;
|
||||
|
||||
const string Policy = """
|
||||
package demo
|
||||
default allow = false
|
||||
allow if {
|
||||
input.user == "alice"
|
||||
some role in data.roles[input.user]
|
||||
role == "admin"
|
||||
}
|
||||
""";
|
||||
|
||||
const string Data = """
|
||||
{ "roles": { "alice": ["admin"] } }
|
||||
""";
|
||||
|
||||
const string Input = """
|
||||
{ "user": "alice" }
|
||||
""";
|
||||
|
||||
var modules = new[] { new PolicyModule("demo.rego", Policy) };
|
||||
var entryPoints = new[] { "data.demo.allow" };
|
||||
|
||||
using var program = Program.CompileFromModules(Data, modules, entryPoints);
|
||||
var listing = program.GenerateListing();
|
||||
|
||||
using var vm = new Rvm();
|
||||
vm.LoadProgram(program);
|
||||
vm.SetDataJson(Data);
|
||||
vm.SetInputJson(Input);
|
||||
|
||||
var result = vm.Execute();
|
||||
Console.WriteLine($"allow: {result}");
|
||||
```
|
||||
|
||||
119
bindings/csharp/Regorus.Tests/RvmProgramTests.cs
Normal file
119
bindings/csharp/Regorus.Tests/RvmProgramTests.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Regorus.Tests;
|
||||
|
||||
[TestClass]
|
||||
public sealed class RvmProgramTests
|
||||
{
|
||||
private const string Policy = """
|
||||
package demo
|
||||
default allow = false
|
||||
allow if {
|
||||
input.user == "alice"
|
||||
some role in data.roles[input.user]
|
||||
role == "admin"
|
||||
count(input.actions) > 0
|
||||
}
|
||||
""";
|
||||
|
||||
private const string Data = """
|
||||
{
|
||||
"roles": {
|
||||
"alice": ["admin", "reader"]
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private const string Input = """
|
||||
{
|
||||
"user": "alice",
|
||||
"actions": ["read"]
|
||||
}
|
||||
""";
|
||||
|
||||
private const string HostAwaitPolicy = """
|
||||
package demo
|
||||
import rego.v1
|
||||
|
||||
default allow := false
|
||||
|
||||
allow if {
|
||||
input.account.active == true
|
||||
details := __builtin_host_await(input.account.id, "account")
|
||||
details.tier == "gold"
|
||||
}
|
||||
""";
|
||||
|
||||
private const string HostAwaitInput = """
|
||||
{
|
||||
"account": {
|
||||
"id": "acct-1",
|
||||
"active": true
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
[TestMethod]
|
||||
public void Program_compile_and_execute_succeeds()
|
||||
{
|
||||
var modules = new[] { new PolicyModule("demo.rego", Policy) };
|
||||
var entryPoints = new[] { "data.demo.allow" };
|
||||
|
||||
var program = Program.CompileFromModules(Data, modules, entryPoints);
|
||||
var listing = program.GenerateListing();
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(listing), "listing should be generated");
|
||||
|
||||
var binary = program.SerializeBinary();
|
||||
var rehydrated = Program.DeserializeBinary(binary, out var isPartial);
|
||||
Assert.IsFalse(isPartial, "program should be fully deserialized");
|
||||
|
||||
using var vm = new Rvm();
|
||||
vm.LoadProgram(rehydrated);
|
||||
vm.SetDataJson(Data);
|
||||
vm.SetInputJson(Input);
|
||||
|
||||
var result = vm.Execute();
|
||||
Assert.AreEqual("true", result, "expected allow=true");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Program_compile_from_engine_succeeds()
|
||||
{
|
||||
using var engine = new Engine();
|
||||
engine.AddPolicy("demo.rego", Policy);
|
||||
|
||||
var program = Program.CompileFromEngine(engine, new[] { "data.demo.allow" });
|
||||
using var vm = new Rvm();
|
||||
vm.LoadProgram(program);
|
||||
vm.SetDataJson(Data);
|
||||
vm.SetInputJson(Input);
|
||||
|
||||
var result = vm.Execute();
|
||||
Assert.AreEqual("true", result, "expected allow=true");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Program_host_await_suspend_and_resume_succeeds()
|
||||
{
|
||||
var modules = new[] { new PolicyModule("host_await.rego", HostAwaitPolicy) };
|
||||
var entryPoints = new[] { "data.demo.allow" };
|
||||
|
||||
using var program = Program.CompileFromModules("{}", modules, entryPoints);
|
||||
using var vm = new Rvm();
|
||||
vm.SetExecutionMode(1);
|
||||
vm.LoadProgram(program);
|
||||
vm.SetInputJson(HostAwaitInput);
|
||||
|
||||
var initial = vm.Execute();
|
||||
var state = vm.GetExecutionState();
|
||||
Assert.IsNotNull(state, "execution state should be available");
|
||||
StringAssert.Contains(state!, "HostAwait", "expected HostAwait suspension");
|
||||
|
||||
var resumed = vm.Resume("{\"tier\":\"gold\"}");
|
||||
Assert.AreEqual("true", resumed, "expected allow=true after resume");
|
||||
}
|
||||
}
|
||||
@@ -180,7 +180,7 @@ namespace Regorus
|
||||
return handle;
|
||||
}
|
||||
|
||||
private T UseHandle<T>(Func<IntPtr, T> func)
|
||||
internal T UseHandle<T>(Func<IntPtr, T> func)
|
||||
{
|
||||
var handle = GetHandleForUse();
|
||||
bool addedRef = false;
|
||||
@@ -204,6 +204,11 @@ namespace Regorus
|
||||
}
|
||||
}
|
||||
|
||||
internal T UseHandleForInterop<T>(Func<IntPtr, T> func)
|
||||
{
|
||||
return UseHandle(func);
|
||||
}
|
||||
|
||||
private void UseHandle(Action<IntPtr> action)
|
||||
{
|
||||
UseHandle<object?>(handlePtr =>
|
||||
|
||||
@@ -441,7 +441,7 @@ namespace Regorus
|
||||
}
|
||||
}
|
||||
|
||||
private RegorusEngineHandle GetHandleForUse()
|
||||
internal RegorusEngineHandle GetHandleForUse()
|
||||
{
|
||||
var handle = _handle;
|
||||
if (handle is null || handle.IsClosed || handle.IsInvalid)
|
||||
@@ -451,7 +451,7 @@ namespace Regorus
|
||||
return handle;
|
||||
}
|
||||
|
||||
private void UseHandle(Action<IntPtr> action)
|
||||
internal void UseHandle(Action<IntPtr> action)
|
||||
{
|
||||
UseHandle<object?>(handlePtr =>
|
||||
{
|
||||
@@ -460,7 +460,7 @@ namespace Regorus
|
||||
});
|
||||
}
|
||||
|
||||
private T UseHandle<T>(Func<IntPtr, T> func)
|
||||
internal T UseHandle<T>(Func<IntPtr, T> func)
|
||||
{
|
||||
var handle = GetHandleForUse();
|
||||
bool addedRef = false;
|
||||
@@ -484,5 +484,10 @@ namespace Regorus
|
||||
}
|
||||
}
|
||||
|
||||
internal T UseHandleForInterop<T>(Func<IntPtr, T> func)
|
||||
{
|
||||
return UseHandle(func);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,13 @@ namespace Regorus.Internal
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_result_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern void regorus_result_drop(RegorusResult result);
|
||||
|
||||
/// <summary>
|
||||
/// Drop a RegorusBuffer.
|
||||
/// data is not valid after drop.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_buffer_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern void regorus_buffer_drop(RegorusBuffer* buffer);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Memory Limit Methods
|
||||
@@ -85,6 +92,12 @@ namespace Regorus.Internal
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_engine_clone", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusEngine* regorus_engine_clone(RegorusEngine* engine);
|
||||
|
||||
/// <summary>
|
||||
/// Compile an RVM program from the engine state with entry points.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_engine_compile_program_with_entrypoints", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_engine_compile_program_with_entrypoints(RegorusEngine* engine, byte** entryPoints, UIntPtr entryPointsLen);
|
||||
|
||||
/// <summary>
|
||||
/// Drop a RegorusEngine.
|
||||
/// </summary>
|
||||
@@ -92,6 +105,138 @@ namespace Regorus.Internal
|
||||
internal static extern void regorus_engine_drop(RegorusEngine* engine);
|
||||
|
||||
/// <summary>
|
||||
|
||||
/// <summary>
|
||||
/// Compile an RVM program from data/modules and entry points.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_program_compile_from_modules", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_program_compile_from_modules(byte* data_json, RegorusPolicyModule* modules, UIntPtr modules_len, byte** entry_points, UIntPtr entry_points_len);
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new empty program.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_program_new", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusProgram* regorus_program_new();
|
||||
|
||||
/// <summary>
|
||||
/// Drop a program handle.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_program_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern void regorus_program_drop(RegorusProgram* program);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a program to binary format.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_program_serialize_binary", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_program_serialize_binary(RegorusProgram* program);
|
||||
|
||||
/// <summary>
|
||||
/// Deserialize a program from binary format.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_program_deserialize_binary", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_program_deserialize_binary(byte* data, UIntPtr len, byte* is_partial);
|
||||
|
||||
/// <summary>
|
||||
/// Generate a readable assembly listing for the program.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_program_generate_listing", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_program_generate_listing(RegorusProgram* program);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new RVM instance.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_new", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusRvm* regorus_rvm_new();
|
||||
|
||||
/// <summary>
|
||||
/// Create a new RVM instance from a compiled policy.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_new_with_policy", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_rvm_new_with_policy(RegorusCompiledPolicy* compiled_policy);
|
||||
|
||||
/// <summary>
|
||||
/// Drop an RVM instance.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern void regorus_rvm_drop(RegorusRvm* vm);
|
||||
|
||||
/// <summary>
|
||||
/// Load a program into the RVM.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_load_program", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_rvm_load_program(RegorusRvm* vm, RegorusProgram* program);
|
||||
|
||||
/// <summary>
|
||||
/// Set the data document for the RVM.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_set_data", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_rvm_set_data(RegorusRvm* vm, byte* data_json);
|
||||
|
||||
/// <summary>
|
||||
/// Set the input document for the RVM.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_set_input", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_rvm_set_input(RegorusRvm* vm, byte* input_json);
|
||||
|
||||
/// <summary>
|
||||
/// Execute the program.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_execute", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_rvm_execute(RegorusRvm* vm);
|
||||
|
||||
/// <summary>
|
||||
/// Execute an entry point by name.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_execute_entry_point_by_name", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_rvm_execute_entry_point_by_name(RegorusRvm* vm, byte* entry_point);
|
||||
|
||||
/// <summary>
|
||||
/// Execute an entry point by index.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_execute_entry_point_by_index", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_rvm_execute_entry_point_by_index(RegorusRvm* vm, UIntPtr index);
|
||||
|
||||
/// <summary>
|
||||
/// Resume execution.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_resume", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_rvm_resume(RegorusRvm* vm, byte* resume_value_json, [MarshalAs(UnmanagedType.I1)] bool has_value);
|
||||
|
||||
/// <summary>
|
||||
/// Get the current execution state.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_get_execution_state", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_rvm_get_execution_state(RegorusRvm* vm);
|
||||
|
||||
/// <summary>
|
||||
/// Set the maximum instruction limit.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_set_max_instructions", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_rvm_set_max_instructions(RegorusRvm* vm, UIntPtr max_instructions);
|
||||
|
||||
/// <summary>
|
||||
/// Set strict builtin error handling.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_set_strict_builtin_errors", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_rvm_set_strict_builtin_errors(RegorusRvm* vm, [MarshalAs(UnmanagedType.I1)] bool strict);
|
||||
|
||||
/// <summary>
|
||||
/// Set execution mode (0 run-to-completion, 1 suspendable).
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_set_execution_mode", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_rvm_set_execution_mode(RegorusRvm* vm, byte mode);
|
||||
|
||||
/// <summary>
|
||||
/// Set step mode for suspendable execution.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_set_step_mode", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_rvm_set_step_mode(RegorusRvm* vm, [MarshalAs(UnmanagedType.I1)] bool enabled);
|
||||
|
||||
/// <summary>
|
||||
/// Set execution timer configuration.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_set_execution_timer_config", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_rvm_set_execution_timer_config(RegorusRvm* vm, [MarshalAs(UnmanagedType.I1)] bool has_config, RegorusExecutionTimerConfig config);
|
||||
/// Add a policy.
|
||||
/// The policy is parsed into AST.
|
||||
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.add_policy
|
||||
@@ -617,6 +762,17 @@ namespace Regorus.Internal
|
||||
public uint check_interval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Byte buffer returned from FFI.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal unsafe struct RegorusBuffer
|
||||
{
|
||||
public byte* data;
|
||||
public UIntPtr len;
|
||||
public UIntPtr capacity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper for regorus::Engine.
|
||||
/// </summary>
|
||||
@@ -633,6 +789,22 @@ namespace Regorus.Internal
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper for regorus::rvm::Program.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal unsafe partial struct RegorusProgram
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper for regorus::rvm::RegoVM.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal unsafe partial struct RegorusRvm
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// FFI wrapper for PolicyModule struct.
|
||||
/// </summary>
|
||||
|
||||
333
bindings/csharp/Regorus/Program.cs
Normal file
333
bindings/csharp/Regorus/Program.cs
Normal file
@@ -0,0 +1,333 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Regorus.Internal;
|
||||
|
||||
#nullable enable
|
||||
namespace Regorus
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a compiled RVM program.
|
||||
/// </summary>
|
||||
public unsafe sealed class Program : IDisposable
|
||||
{
|
||||
private RegorusProgramHandle? _handle;
|
||||
private int _isDisposed;
|
||||
|
||||
private Program(RegorusProgramHandle handle)
|
||||
{
|
||||
_handle = handle ?? throw new ArgumentNullException(nameof(handle));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an empty program.
|
||||
/// </summary>
|
||||
public static Program CreateEmpty()
|
||||
{
|
||||
return new Program(RegorusProgramHandle.Create());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compile an RVM program from modules and entry points.
|
||||
/// </summary>
|
||||
public static Program CompileFromModules(string dataJson, IEnumerable<PolicyModule> modules, IEnumerable<string> entryPoints)
|
||||
{
|
||||
var modulesArray = modules.ToArray();
|
||||
var entryPointsArray = entryPoints.ToArray();
|
||||
if (entryPointsArray.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("At least one entry point is required.", nameof(entryPoints));
|
||||
}
|
||||
|
||||
var nativeModules = new RegorusPolicyModule[modulesArray.Length];
|
||||
var pinnedStrings = new List<Utf8Marshaller.PinnedUtf8>(modulesArray.Length * 2 + entryPointsArray.Length);
|
||||
var entryPointers = new IntPtr[entryPointsArray.Length];
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < modulesArray.Length; i++)
|
||||
{
|
||||
var idPinned = Utf8Marshaller.Pin(modulesArray[i].Id);
|
||||
var contentPinned = Utf8Marshaller.Pin(modulesArray[i].Content);
|
||||
pinnedStrings.Add(idPinned);
|
||||
pinnedStrings.Add(contentPinned);
|
||||
|
||||
nativeModules[i] = new RegorusPolicyModule
|
||||
{
|
||||
id = idPinned.Pointer,
|
||||
content = contentPinned.Pointer
|
||||
};
|
||||
}
|
||||
|
||||
for (int i = 0; i < entryPointsArray.Length; i++)
|
||||
{
|
||||
var entryPinned = Utf8Marshaller.Pin(entryPointsArray[i]);
|
||||
pinnedStrings.Add(entryPinned);
|
||||
entryPointers[i] = (IntPtr)entryPinned.Pointer;
|
||||
}
|
||||
|
||||
return Utf8Marshaller.WithUtf8(dataJson, dataPtr =>
|
||||
{
|
||||
fixed (RegorusPolicyModule* modulesPtr = nativeModules)
|
||||
fixed (IntPtr* entryPtr = entryPointers)
|
||||
{
|
||||
var result = API.regorus_program_compile_from_modules(
|
||||
(byte*)dataPtr,
|
||||
modulesPtr,
|
||||
(UIntPtr)modulesArray.Length,
|
||||
(byte**)entryPtr,
|
||||
(UIntPtr)entryPointsArray.Length);
|
||||
|
||||
return GetProgramResult(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (var pinned in pinnedStrings)
|
||||
{
|
||||
pinned.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compile an RVM program from an engine instance and entry points.
|
||||
/// </summary>
|
||||
public static Program CompileFromEngine(Engine engine, IEnumerable<string> entryPoints)
|
||||
{
|
||||
if (engine is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(engine));
|
||||
}
|
||||
|
||||
var entryPointsArray = entryPoints.ToArray();
|
||||
if (entryPointsArray.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("At least one entry point is required.", nameof(entryPoints));
|
||||
}
|
||||
|
||||
var pinnedStrings = new List<Utf8Marshaller.PinnedUtf8>(entryPointsArray.Length);
|
||||
var entryPointers = new IntPtr[entryPointsArray.Length];
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < entryPointsArray.Length; i++)
|
||||
{
|
||||
var entryPinned = Utf8Marshaller.Pin(entryPointsArray[i]);
|
||||
pinnedStrings.Add(entryPinned);
|
||||
entryPointers[i] = (IntPtr)entryPinned.Pointer;
|
||||
}
|
||||
|
||||
return engine.UseHandleForInterop(enginePtr =>
|
||||
{
|
||||
fixed (IntPtr* entryPtr = entryPointers)
|
||||
{
|
||||
var result = API.regorus_engine_compile_program_with_entrypoints(
|
||||
(RegorusEngine*)enginePtr,
|
||||
(byte**)entryPtr,
|
||||
(UIntPtr)entryPointsArray.Length);
|
||||
|
||||
return GetProgramResult(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (var pinned in pinnedStrings)
|
||||
{
|
||||
pinned.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserialize an RVM program from binary format.
|
||||
/// </summary>
|
||||
public static Program DeserializeBinary(byte[] data, out bool isPartial)
|
||||
{
|
||||
if (data is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(data));
|
||||
}
|
||||
|
||||
byte partialFlag = 0;
|
||||
fixed (byte* dataPtr = data)
|
||||
{
|
||||
var result = API.regorus_program_deserialize_binary(dataPtr, (UIntPtr)data.Length, &partialFlag);
|
||||
var program = GetProgramResult(result);
|
||||
isPartial = partialFlag != 0;
|
||||
return program;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize the program to binary format.
|
||||
/// </summary>
|
||||
public byte[] SerializeBinary()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return UseHandle(programPtr =>
|
||||
{
|
||||
var result = API.regorus_program_serialize_binary((RegorusProgram*)programPtr);
|
||||
return ExtractBuffer(result);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a readable assembly listing.
|
||||
/// </summary>
|
||||
public string? GenerateListing()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return UseHandle(programPtr =>
|
||||
{
|
||||
return CheckAndDropResult(API.regorus_program_generate_listing((RegorusProgram*)programPtr));
|
||||
});
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (System.Threading.Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0)
|
||||
{
|
||||
_handle?.Dispose();
|
||||
_handle = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_isDisposed != 0 || _handle is null || _handle.IsClosed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(Program));
|
||||
}
|
||||
}
|
||||
|
||||
internal RegorusProgramHandle GetHandleForUse()
|
||||
{
|
||||
var handle = _handle;
|
||||
if (handle is null || handle.IsClosed || handle.IsInvalid)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(Program));
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
internal T UseHandle<T>(Func<IntPtr, T> func)
|
||||
{
|
||||
var handle = GetHandleForUse();
|
||||
bool addedRef = false;
|
||||
try
|
||||
{
|
||||
handle.DangerousAddRef(ref addedRef);
|
||||
var pointer = handle.DangerousGetHandle();
|
||||
if (pointer == IntPtr.Zero)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(Program));
|
||||
}
|
||||
|
||||
return func(pointer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (addedRef)
|
||||
{
|
||||
handle.DangerousRelease();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Program GetProgramResult(RegorusResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (result.status != RegorusStatus.Ok)
|
||||
{
|
||||
var message = Utf8Marshaller.FromUtf8(result.error_message);
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
if (result.data_type != RegorusDataType.Pointer || result.pointer_value == null)
|
||||
{
|
||||
throw new Exception("Expected program pointer but got different data type");
|
||||
}
|
||||
|
||||
var handle = RegorusProgramHandle.FromPointer((IntPtr)result.pointer_value);
|
||||
return new Program(handle);
|
||||
}
|
||||
finally
|
||||
{
|
||||
API.regorus_result_drop(result);
|
||||
}
|
||||
}
|
||||
|
||||
private static string? CheckAndDropResult(RegorusResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (result.status != RegorusStatus.Ok)
|
||||
{
|
||||
var message = Utf8Marshaller.FromUtf8(result.error_message);
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
return result.data_type switch
|
||||
{
|
||||
RegorusDataType.String => Utf8Marshaller.FromUtf8(result.output),
|
||||
RegorusDataType.Boolean => result.bool_value.ToString().ToLowerInvariant(),
|
||||
RegorusDataType.Integer => result.int_value.ToString(),
|
||||
RegorusDataType.None => null,
|
||||
_ => Utf8Marshaller.FromUtf8(result.output)
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
API.regorus_result_drop(result);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] ExtractBuffer(RegorusResult result)
|
||||
{
|
||||
RegorusBuffer* buffer = null;
|
||||
try
|
||||
{
|
||||
if (result.status != RegorusStatus.Ok)
|
||||
{
|
||||
var message = Utf8Marshaller.FromUtf8(result.error_message);
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
if (result.data_type != RegorusDataType.Pointer || result.pointer_value == null)
|
||||
{
|
||||
throw new Exception("Expected buffer pointer but got different data type");
|
||||
}
|
||||
|
||||
buffer = (RegorusBuffer*)result.pointer_value;
|
||||
var length = checked((int)buffer->len);
|
||||
var data = new byte[length];
|
||||
if (length > 0)
|
||||
{
|
||||
Marshal.Copy((IntPtr)buffer->data, data, 0, length);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (buffer != null)
|
||||
{
|
||||
API.regorus_buffer_drop(buffer);
|
||||
}
|
||||
API.regorus_result_drop(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
292
bindings/csharp/Regorus/Rvm.cs
Normal file
292
bindings/csharp/Regorus/Rvm.cs
Normal file
@@ -0,0 +1,292 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using Regorus.Internal;
|
||||
|
||||
#nullable enable
|
||||
namespace Regorus
|
||||
{
|
||||
/// <summary>
|
||||
/// Wrapper for the Regorus RVM runtime.
|
||||
/// </summary>
|
||||
public unsafe sealed class Rvm : IDisposable
|
||||
{
|
||||
private RegorusRvmHandle? _handle;
|
||||
private int _isDisposed;
|
||||
|
||||
public Rvm()
|
||||
{
|
||||
_handle = RegorusRvmHandle.Create();
|
||||
}
|
||||
|
||||
private Rvm(RegorusRvmHandle handle)
|
||||
{
|
||||
_handle = handle ?? throw new ArgumentNullException(nameof(handle));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an RVM instance backed by a compiled policy (for default rule evaluation).
|
||||
/// </summary>
|
||||
public static Rvm CreateWithPolicy(CompiledPolicy policy)
|
||||
{
|
||||
if (policy is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(policy));
|
||||
}
|
||||
|
||||
return policy.UseHandleForInterop(policyPtr =>
|
||||
{
|
||||
var result = API.regorus_rvm_new_with_policy((RegorusCompiledPolicy*)policyPtr);
|
||||
return GetRvmResult(result);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load a program into the VM.
|
||||
/// </summary>
|
||||
public void LoadProgram(Program program)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (program is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(program));
|
||||
}
|
||||
|
||||
program.UseHandle(programPtr =>
|
||||
{
|
||||
UseHandle(vmPtr =>
|
||||
{
|
||||
CheckAndDropResult(API.regorus_rvm_load_program((RegorusRvm*)vmPtr, (RegorusProgram*)programPtr));
|
||||
return 0;
|
||||
});
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the data document for the VM.
|
||||
/// </summary>
|
||||
public void SetDataJson(string dataJson)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Utf8Marshaller.WithUtf8(dataJson, dataPtr =>
|
||||
{
|
||||
UseHandle(vmPtr =>
|
||||
{
|
||||
CheckAndDropResult(API.regorus_rvm_set_data((RegorusRvm*)vmPtr, (byte*)dataPtr));
|
||||
return 0;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the input document for the VM.
|
||||
/// </summary>
|
||||
public void SetInputJson(string inputJson)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Utf8Marshaller.WithUtf8(inputJson, inputPtr =>
|
||||
{
|
||||
UseHandle(vmPtr =>
|
||||
{
|
||||
CheckAndDropResult(API.regorus_rvm_set_input((RegorusRvm*)vmPtr, (byte*)inputPtr));
|
||||
return 0;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the execution mode (0 = run-to-completion, 1 = suspendable).
|
||||
/// </summary>
|
||||
public void SetExecutionMode(byte mode)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
UseHandle(vmPtr =>
|
||||
{
|
||||
CheckAndDropResult(API.regorus_rvm_set_execution_mode((RegorusRvm*)vmPtr, mode));
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute the program and return the JSON result.
|
||||
/// </summary>
|
||||
public string? Execute()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return UseHandle(vmPtr =>
|
||||
{
|
||||
return CheckAndDropResult(API.regorus_rvm_execute((RegorusRvm*)vmPtr));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute a named entry point.
|
||||
/// </summary>
|
||||
public string? ExecuteEntryPoint(string entryPoint)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return Utf8Marshaller.WithUtf8(entryPoint, entryPtr =>
|
||||
{
|
||||
return UseHandle(vmPtr =>
|
||||
{
|
||||
return CheckAndDropResult(API.regorus_rvm_execute_entry_point_by_name((RegorusRvm*)vmPtr, (byte*)entryPtr));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute an entry point by index.
|
||||
/// </summary>
|
||||
public string? ExecuteEntryPoint(ulong index)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return UseHandle(vmPtr =>
|
||||
{
|
||||
return CheckAndDropResult(API.regorus_rvm_execute_entry_point_by_index((RegorusRvm*)vmPtr, (UIntPtr)index));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resume execution with an optional value.
|
||||
/// </summary>
|
||||
public string? Resume(string? resumeValueJson)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (resumeValueJson is null)
|
||||
{
|
||||
return UseHandle(vmPtr =>
|
||||
{
|
||||
return CheckAndDropResult(API.regorus_rvm_resume((RegorusRvm*)vmPtr, null, has_value: false));
|
||||
});
|
||||
}
|
||||
|
||||
return Utf8Marshaller.WithUtf8(resumeValueJson, valuePtr =>
|
||||
{
|
||||
return UseHandle(vmPtr =>
|
||||
{
|
||||
return CheckAndDropResult(API.regorus_rvm_resume((RegorusRvm*)vmPtr, (byte*)valuePtr, has_value: true));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current execution state.
|
||||
/// </summary>
|
||||
public string? GetExecutionState()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return UseHandle(vmPtr =>
|
||||
{
|
||||
return CheckAndDropResult(API.regorus_rvm_get_execution_state((RegorusRvm*)vmPtr));
|
||||
});
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (System.Threading.Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0)
|
||||
{
|
||||
_handle?.Dispose();
|
||||
_handle = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_isDisposed != 0 || _handle is null || _handle.IsClosed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(Rvm));
|
||||
}
|
||||
}
|
||||
|
||||
internal RegorusRvmHandle GetHandleForUse()
|
||||
{
|
||||
var handle = _handle;
|
||||
if (handle is null || handle.IsClosed || handle.IsInvalid)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(Rvm));
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
internal T UseHandle<T>(Func<IntPtr, T> func)
|
||||
{
|
||||
var handle = GetHandleForUse();
|
||||
bool addedRef = false;
|
||||
try
|
||||
{
|
||||
handle.DangerousAddRef(ref addedRef);
|
||||
var pointer = handle.DangerousGetHandle();
|
||||
if (pointer == IntPtr.Zero)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(Rvm));
|
||||
}
|
||||
|
||||
return func(pointer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (addedRef)
|
||||
{
|
||||
handle.DangerousRelease();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Rvm GetRvmResult(RegorusResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (result.status != RegorusStatus.Ok)
|
||||
{
|
||||
var message = Utf8Marshaller.FromUtf8(result.error_message);
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
if (result.data_type != RegorusDataType.Pointer || result.pointer_value == null)
|
||||
{
|
||||
throw new Exception("Expected RVM pointer but got different data type");
|
||||
}
|
||||
|
||||
var handle = RegorusRvmHandle.FromPointer((IntPtr)result.pointer_value);
|
||||
return new Rvm(handle);
|
||||
}
|
||||
finally
|
||||
{
|
||||
API.regorus_result_drop(result);
|
||||
}
|
||||
}
|
||||
|
||||
private static string? CheckAndDropResult(RegorusResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (result.status != RegorusStatus.Ok)
|
||||
{
|
||||
var message = Utf8Marshaller.FromUtf8(result.error_message);
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
return result.data_type switch
|
||||
{
|
||||
RegorusDataType.String => Utf8Marshaller.FromUtf8(result.output),
|
||||
RegorusDataType.Boolean => result.bool_value.ToString().ToLowerInvariant(),
|
||||
RegorusDataType.Integer => result.int_value.ToString(),
|
||||
RegorusDataType.None => null,
|
||||
_ => Utf8Marshaller.FromUtf8(result.output)
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
API.regorus_result_drop(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,4 +87,100 @@ namespace Regorus
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class RegorusProgramHandle : SafeHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
private RegorusProgramHandle() : base(ownsHandle: true)
|
||||
{
|
||||
}
|
||||
|
||||
internal static RegorusProgramHandle Create()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var raw = Internal.API.regorus_program_new();
|
||||
if (raw is null)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to create Regorus program.");
|
||||
}
|
||||
|
||||
var handle = new RegorusProgramHandle();
|
||||
handle.SetHandle((IntPtr)raw);
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
|
||||
internal static RegorusProgramHandle FromPointer(IntPtr pointer)
|
||||
{
|
||||
if (pointer == IntPtr.Zero)
|
||||
{
|
||||
throw new ArgumentException("Pointer cannot be zero.", nameof(pointer));
|
||||
}
|
||||
|
||||
var handle = new RegorusProgramHandle();
|
||||
handle.SetHandle(pointer);
|
||||
return handle;
|
||||
}
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
if (!IsInvalid && !IsClosed)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
Internal.API.regorus_program_drop((Internal.RegorusProgram*)handle);
|
||||
}
|
||||
SetHandle(IntPtr.Zero);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class RegorusRvmHandle : SafeHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
private RegorusRvmHandle() : base(ownsHandle: true)
|
||||
{
|
||||
}
|
||||
|
||||
internal static RegorusRvmHandle Create()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var raw = Internal.API.regorus_rvm_new();
|
||||
if (raw is null)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to create Regorus RVM.");
|
||||
}
|
||||
|
||||
var handle = new RegorusRvmHandle();
|
||||
handle.SetHandle((IntPtr)raw);
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
|
||||
internal static RegorusRvmHandle FromPointer(IntPtr pointer)
|
||||
{
|
||||
if (pointer == IntPtr.Zero)
|
||||
{
|
||||
throw new ArgumentException("Pointer cannot be zero.", nameof(pointer));
|
||||
}
|
||||
|
||||
var handle = new RegorusRvmHandle();
|
||||
handle.SetHandle(pointer);
|
||||
return handle;
|
||||
}
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
if (!IsInvalid && !IsClosed)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
Internal.API.regorus_rvm_drop((Internal.RegorusRvm*)handle);
|
||||
}
|
||||
SetHandle(IntPtr.Zero);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,55 @@ triplet_count := count([1 |
|
||||
private const string EXECUTION_TIMER_QUERY = "data.limits.timer.triplet_count";
|
||||
private const int EXECUTION_TIMER_VALUE_COUNT = 40;
|
||||
|
||||
private const string RVM_POLICY = """
|
||||
package demo
|
||||
import rego.v1
|
||||
|
||||
default allow := false
|
||||
|
||||
allow if {
|
||||
input.user == "alice"
|
||||
some role in data.roles[input.user]
|
||||
role == "admin"
|
||||
}
|
||||
""";
|
||||
|
||||
private const string RVM_DATA = """
|
||||
{
|
||||
"roles": {
|
||||
"alice": ["admin", "reader"]
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private const string RVM_INPUT = """
|
||||
{
|
||||
"user": "alice"
|
||||
}
|
||||
""";
|
||||
|
||||
private const string HOST_AWAIT_POLICY = """
|
||||
package demo
|
||||
import rego.v1
|
||||
|
||||
default allow := false
|
||||
|
||||
allow if {
|
||||
input.account.active == true
|
||||
details := __builtin_host_await(input.account.id, "account")
|
||||
details.tier == "gold"
|
||||
}
|
||||
""";
|
||||
|
||||
private const string HOST_AWAIT_INPUT = """
|
||||
{
|
||||
"account": {
|
||||
"id": "acct-1",
|
||||
"active": true
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
// Test data constants
|
||||
private const string COMPLIANT_STORAGE_ACCOUNT = @"{
|
||||
""type"": ""Microsoft.Storage/storageAccounts"",
|
||||
@@ -174,6 +223,15 @@ triplet_count := count([1 |
|
||||
|
||||
Console.WriteLine("\n5. Execution timer configuration:");
|
||||
DemonstrateExecutionTimer();
|
||||
|
||||
Console.WriteLine("\n6. RVM program execution:");
|
||||
DemonstrateRvmUsage();
|
||||
|
||||
Console.WriteLine("\n7. RVM program compilation from engine:");
|
||||
DemonstrateRvmCompileFromEngine();
|
||||
|
||||
Console.WriteLine("\n8. RVM host await (suspend/resume):");
|
||||
DemonstrateRvmHostAwait();
|
||||
}
|
||||
|
||||
static void DemonstrateConcurrentEvaluation(Regorus.CompiledPolicy compiledPolicy)
|
||||
@@ -359,4 +417,77 @@ triplet_count := count([1 |
|
||||
Regorus.Engine.ClearFallbackExecutionTimerConfig();
|
||||
}
|
||||
}
|
||||
|
||||
static void DemonstrateRvmUsage()
|
||||
{
|
||||
var modules = new List<Regorus.PolicyModule>
|
||||
{
|
||||
new Regorus.PolicyModule("demo.rego", RVM_POLICY)
|
||||
};
|
||||
var entryPoints = new[] { "data.demo.allow" };
|
||||
|
||||
using var program = Regorus.Program.CompileFromModules(RVM_DATA, modules, entryPoints);
|
||||
var binary = program.SerializeBinary();
|
||||
using var rehydrated = Regorus.Program.DeserializeBinary(binary, out var isPartial);
|
||||
if (isPartial)
|
||||
{
|
||||
throw new InvalidOperationException("RVM program deserialization returned a partial program.");
|
||||
}
|
||||
|
||||
Console.WriteLine($"Serialized program size: {binary.Length} bytes");
|
||||
|
||||
var listing = rehydrated.GenerateListing();
|
||||
|
||||
Console.WriteLine("RVM listing:");
|
||||
Console.WriteLine(listing);
|
||||
|
||||
using var vm = new Regorus.Rvm();
|
||||
vm.LoadProgram(rehydrated);
|
||||
vm.SetDataJson(RVM_DATA);
|
||||
vm.SetInputJson(RVM_INPUT);
|
||||
|
||||
var result = vm.Execute();
|
||||
Console.WriteLine($"RVM result: {result}");
|
||||
}
|
||||
|
||||
static void DemonstrateRvmCompileFromEngine()
|
||||
{
|
||||
using var engine = new Regorus.Engine();
|
||||
engine.AddPolicy("demo.rego", RVM_POLICY);
|
||||
engine.AddDataJson(RVM_DATA);
|
||||
|
||||
var entryPoints = new[] { "data.demo.allow" };
|
||||
using var program = Regorus.Program.CompileFromEngine(engine, entryPoints);
|
||||
|
||||
using var vm = new Regorus.Rvm();
|
||||
vm.LoadProgram(program);
|
||||
vm.SetDataJson(RVM_DATA);
|
||||
vm.SetInputJson(RVM_INPUT);
|
||||
|
||||
var result = vm.ExecuteEntryPoint("data.demo.allow");
|
||||
Console.WriteLine($"RVM result from engine-compiled program: {result}");
|
||||
}
|
||||
|
||||
static void DemonstrateRvmHostAwait()
|
||||
{
|
||||
var modules = new List<Regorus.PolicyModule>
|
||||
{
|
||||
new Regorus.PolicyModule("host_await.rego", HOST_AWAIT_POLICY)
|
||||
};
|
||||
var entryPoints = new[] { "data.demo.allow" };
|
||||
|
||||
using var program = Regorus.Program.CompileFromModules("{}", modules, entryPoints);
|
||||
using var vm = new Regorus.Rvm();
|
||||
vm.SetExecutionMode(1);
|
||||
vm.LoadProgram(program);
|
||||
vm.SetInputJson(HOST_AWAIT_INPUT);
|
||||
|
||||
var initial = vm.Execute();
|
||||
var state = vm.GetExecutionState();
|
||||
Console.WriteLine($"HostAwait initial result: {initial}");
|
||||
Console.WriteLine($"Execution state: {state}");
|
||||
|
||||
var resumed = vm.Resume("{\"tier\":\"gold\"}");
|
||||
Console.WriteLine($"HostAwait resumed result: {resumed}");
|
||||
}
|
||||
}
|
||||
|
||||
48
bindings/ffi/Cargo.lock
generated
48
bindings/ffi/Cargo.lock
generated
@@ -102,6 +102,16 @@ version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"unty",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.8.0"
|
||||
@@ -172,9 +182,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.54"
|
||||
version = "1.2.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583"
|
||||
checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
@@ -211,18 +221,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.54"
|
||||
version = "4.5.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394"
|
||||
checksum = "a75ca66430e33a14957acc24c5077b503e7d374151b2b4b3a10c83b4ceb4be0e"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.54"
|
||||
version = "4.5.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00"
|
||||
checksum = "793207c7fa6300a0608d1080b858e5fdbe713cdc1c8db9fb17777d8a13e63df0"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -329,9 +339,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.8"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
|
||||
[[package]]
|
||||
name = "fluent-uri"
|
||||
@@ -407,9 +417,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.64"
|
||||
version = "0.1.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
|
||||
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
@@ -539,6 +549,8 @@ checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.16.1",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -941,11 +953,13 @@ name = "regorus"
|
||||
version = "0.9.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
"chrono",
|
||||
"chrono-tz",
|
||||
"dashmap",
|
||||
"data-encoding",
|
||||
"globset",
|
||||
"indexmap",
|
||||
"ipnet",
|
||||
"jsonschema",
|
||||
"lazy_static",
|
||||
@@ -1244,6 +1258,12 @@ version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
||||
|
||||
[[package]]
|
||||
name = "unty"
|
||||
version = "0.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.8"
|
||||
@@ -1468,18 +1488,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.34"
|
||||
version = "0.8.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71ddd76bcebeed25db614f82bf31a9f4222d3fbba300e6fb6c00afa26cbd4d9d"
|
||||
checksum = "dafd85c832c1b68bbb4ec0c72c7f6f4fc5179627d2bc7c26b30e4c0cc11e76cc"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.34"
|
||||
version = "0.8.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8187381b52e32220d50b255276aa16a084ec0a9017a0ca2152a1f55c539758d"
|
||||
checksum = "7cb7e4e8436d9db52fbd6625dbf2f45243ab84994a72882ec8227b99e72b439a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -30,6 +30,7 @@ default = [
|
||||
"std",
|
||||
"coverage",
|
||||
"allocator-memory-limits",
|
||||
"rvm",
|
||||
"regorus/arc",
|
||||
"regorus/full-opa",
|
||||
"contention_checks",
|
||||
@@ -40,6 +41,7 @@ std = ["regorus/std"]
|
||||
coverage = ["regorus/coverage"]
|
||||
allocator-memory-limits = ["regorus/allocator-memory-limits"]
|
||||
contention_checks = ["parking_lot"]
|
||||
rvm = ["regorus/rvm"]
|
||||
custom_allocator = []
|
||||
|
||||
[build-dependencies]
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use alloc::boxed::Box;
|
||||
use alloc::ffi::CString;
|
||||
use alloc::format;
|
||||
use alloc::string::{String, ToString};
|
||||
use alloc::vec::Vec;
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use core::ffi::{c_char, c_longlong, c_void, CStr};
|
||||
use core::ptr;
|
||||
use core::{mem, ptr};
|
||||
|
||||
/// Status of a call on `RegorusEngine`.
|
||||
#[repr(C)]
|
||||
@@ -90,6 +92,19 @@ pub struct RegorusResult {
|
||||
pub(crate) error_message: *mut c_char,
|
||||
}
|
||||
|
||||
/// Byte buffer returned from FFI for binary payloads.
|
||||
///
|
||||
/// Must be freed using `regorus_buffer_drop`.
|
||||
#[repr(C)]
|
||||
pub struct RegorusBuffer {
|
||||
/// Pointer to byte buffer data.
|
||||
pub data: *mut u8,
|
||||
/// Number of bytes stored in `data`.
|
||||
pub len: usize,
|
||||
/// Capacity of the allocation backing `data`.
|
||||
pub capacity: usize,
|
||||
}
|
||||
|
||||
impl RegorusResult {
|
||||
/// Create a successful result with no data.
|
||||
pub(crate) fn ok_void() -> Self {
|
||||
@@ -185,6 +200,18 @@ impl RegorusResult {
|
||||
}
|
||||
}
|
||||
|
||||
impl RegorusBuffer {
|
||||
pub(crate) fn from_vec(mut data: Vec<u8>) -> *mut RegorusBuffer {
|
||||
let buffer = RegorusBuffer {
|
||||
data: data.as_mut_ptr(),
|
||||
len: data.len(),
|
||||
capacity: data.capacity(),
|
||||
};
|
||||
mem::forget(data);
|
||||
Box::into_raw(Box::new(buffer))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn to_c_str(s: String) -> *mut c_char {
|
||||
match CString::new(s) {
|
||||
Ok(cs) => cs.into_raw(),
|
||||
@@ -222,6 +249,21 @@ pub(crate) fn to_regorus_string_result(r: Result<String>) -> RegorusResult {
|
||||
}
|
||||
}
|
||||
|
||||
/// Drop a `RegorusBuffer`.
|
||||
///
|
||||
/// `data` is not valid after drop.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_buffer_drop(buffer: *mut RegorusBuffer) {
|
||||
if let Ok(buffer) = to_ref(buffer) {
|
||||
unsafe {
|
||||
if !buffer.data.is_null() {
|
||||
let _ = Vec::from_raw_parts(buffer.data, buffer.len, buffer.capacity);
|
||||
}
|
||||
let _ = Box::from_raw(ptr::from_mut(buffer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Drop a `RegorusResult`.
|
||||
///
|
||||
/// `output` and `error_message` strings are not valid after drop.
|
||||
|
||||
@@ -11,9 +11,16 @@ use crate::panic_guard::with_unwind_guard;
|
||||
use alloc::boxed::Box;
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
#[cfg(feature = "rvm")]
|
||||
use alloc::sync::Arc;
|
||||
use alloc::vec::Vec;
|
||||
use anyhow::{anyhow, Result};
|
||||
use core::ffi::{c_char, c_void};
|
||||
use core::ptr;
|
||||
#[cfg(feature = "rvm")]
|
||||
use regorus::languages::rego::compiler::Compiler;
|
||||
#[cfg(feature = "rvm")]
|
||||
use regorus::rvm::program::Program;
|
||||
|
||||
/// Wrapper for `regorus::Engine`.
|
||||
pub struct RegorusEngine {
|
||||
@@ -720,3 +727,65 @@ pub extern "C" fn regorus_engine_compile_with_entrypoint(
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Compile an RVM program from the engine state with entry points.
|
||||
///
|
||||
/// * `entry_points` - Array of entry point rule paths
|
||||
/// * `entry_points_len` - Number of entry points
|
||||
#[cfg(feature = "rvm")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_engine_compile_program_with_entrypoints(
|
||||
engine: *mut RegorusEngine,
|
||||
entry_points: *const *const c_char,
|
||||
entry_points_len: usize,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let result = || -> Result<Arc<Program>> {
|
||||
if entry_points_len == 0 {
|
||||
return Err(anyhow!("entry_points must contain at least one entry"));
|
||||
}
|
||||
|
||||
if entry_points.is_null() && entry_points_len > 0 {
|
||||
return Err(anyhow!("null entry_points pointer"));
|
||||
}
|
||||
|
||||
let mut entry_points_vec = Vec::with_capacity(entry_points_len);
|
||||
for i in 0..entry_points_len {
|
||||
unsafe {
|
||||
let entry_ptr = entry_points.add(i);
|
||||
if entry_ptr.is_null() {
|
||||
return Err(anyhow!("null entry point at index {i}"));
|
||||
}
|
||||
let entry = from_c_str(*entry_ptr)?;
|
||||
entry_points_vec.push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
let entry_points_ref: Vec<&str> = entry_points_vec.iter().map(|s| s.as_str()).collect();
|
||||
|
||||
let rule = entry_points_ref
|
||||
.first()
|
||||
.ok_or_else(|| anyhow!("entry_points must contain at least one entry"))?;
|
||||
let rule_rc: regorus::Rc<str> = (*rule).into();
|
||||
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
let compiled_policy = guard.compile_with_entrypoint(&rule_rc)?;
|
||||
|
||||
let program = Compiler::compile_from_policy(&compiled_policy, &entry_points_ref)?;
|
||||
Ok(program)
|
||||
}();
|
||||
|
||||
match result {
|
||||
Ok(program) => {
|
||||
let wrapped = crate::rvm::RegorusProgram { program };
|
||||
let boxed = Box::new(wrapped);
|
||||
RegorusResult::ok_pointer(Box::into_raw(boxed) as *mut c_void)
|
||||
}
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::CompilationFailed,
|
||||
format!("Failed to compile RVM program: {e}"),
|
||||
),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -14,5 +14,7 @@ mod engine;
|
||||
mod limits;
|
||||
mod lock;
|
||||
mod panic_guard;
|
||||
#[cfg(feature = "rvm")]
|
||||
pub(crate) mod rvm;
|
||||
mod schema_registry;
|
||||
mod target_registry;
|
||||
|
||||
606
bindings/ffi/src/rvm.rs
Normal file
606
bindings/ffi/src/rvm.rs
Normal file
@@ -0,0 +1,606 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use crate::common::{
|
||||
from_c_str, to_ref, to_regorus_result, RegorusBuffer, RegorusResult, RegorusStatus,
|
||||
};
|
||||
use crate::compile::RegorusPolicyModule;
|
||||
use crate::compiled_policy::RegorusCompiledPolicy;
|
||||
use crate::limits::RegorusExecutionTimerConfig;
|
||||
use crate::lock::{new_handle, try_read, try_write, Handle, ReadGuard, WriteGuard};
|
||||
use crate::panic_guard::with_unwind_guard;
|
||||
use alloc::boxed::Box;
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
use alloc::sync::Arc;
|
||||
use alloc::vec::Vec;
|
||||
use anyhow::{anyhow, Result};
|
||||
use core::ffi::{c_char, c_void};
|
||||
use core::ptr;
|
||||
use regorus::languages::rego::compiler::Compiler;
|
||||
use regorus::rvm::program::{
|
||||
generate_assembly_listing, generate_tabular_assembly_listing, AssemblyListingConfig,
|
||||
DeserializationResult, Program,
|
||||
};
|
||||
use regorus::rvm::vm::{ExecutionMode, ExecutionState, RegoVM};
|
||||
use regorus::PolicyModule;
|
||||
use regorus::Value;
|
||||
|
||||
/// Wrapper for `regorus::rvm::Program`.
|
||||
#[derive(Clone)]
|
||||
pub struct RegorusProgram {
|
||||
pub(crate) program: Arc<Program>,
|
||||
}
|
||||
|
||||
/// Wrapper for `regorus::rvm::RegoVM`.
|
||||
pub struct RegorusRvm {
|
||||
vm: Handle<RegoVM>,
|
||||
}
|
||||
|
||||
impl RegorusRvm {
|
||||
fn new(vm: RegoVM) -> Self {
|
||||
Self { vm: new_handle(vm) }
|
||||
}
|
||||
|
||||
fn contention_error() -> anyhow::Error {
|
||||
anyhow!("regorus rvm handle is already in use; create a separate VM per thread")
|
||||
}
|
||||
|
||||
fn try_write(&self) -> Result<WriteGuard<'_, RegoVM>> {
|
||||
try_write(&self.vm).ok_or_else(Self::contention_error)
|
||||
}
|
||||
|
||||
fn try_read(&self) -> Result<ReadGuard<'_, RegoVM>> {
|
||||
try_read(&self.vm).ok_or_else(Self::contention_error)
|
||||
}
|
||||
}
|
||||
|
||||
/// Drop a `RegorusProgram`.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_program_drop(program: *mut RegorusProgram) {
|
||||
if let Ok(program) = to_ref(program) {
|
||||
unsafe {
|
||||
let _ = Box::from_raw(ptr::from_mut(program));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Drop a `RegorusRvm`.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_rvm_drop(vm: *mut RegorusRvm) {
|
||||
if let Ok(vm) = to_ref(vm) {
|
||||
unsafe {
|
||||
let _ = Box::from_raw(ptr::from_mut(vm));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Compile a compiled policy into an RVM program.
|
||||
///
|
||||
/// * `compiled_policy` - Compiled policy handle
|
||||
/// * `entry_points` - Array of entry point rule paths
|
||||
/// * `entry_points_len` - Number of entry points
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_program_compile_from_policy(
|
||||
compiled_policy: *mut RegorusCompiledPolicy,
|
||||
entry_points: *const *const c_char,
|
||||
entry_points_len: usize,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<*mut RegorusProgram> {
|
||||
if entry_points.is_null() && entry_points_len > 0 {
|
||||
return Err(anyhow!("null entry_points pointer"));
|
||||
}
|
||||
|
||||
let mut entry_points_vec = Vec::with_capacity(entry_points_len);
|
||||
for i in 0..entry_points_len {
|
||||
unsafe {
|
||||
let entry_ptr = entry_points.add(i);
|
||||
if entry_ptr.is_null() {
|
||||
return Err(anyhow!("null entry point at index {i}"));
|
||||
}
|
||||
let entry = from_c_str(*entry_ptr)?;
|
||||
entry_points_vec.push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
let entry_points_ref: Vec<&str> = entry_points_vec.iter().map(|s| s.as_str()).collect();
|
||||
|
||||
let compiled_policy = &to_ref(compiled_policy)?.compiled_policy;
|
||||
let program = Compiler::compile_from_policy(compiled_policy, &entry_points_ref)?;
|
||||
Ok(Box::into_raw(Box::new(RegorusProgram { program })))
|
||||
}();
|
||||
|
||||
match output {
|
||||
Ok(program) => RegorusResult::ok_pointer(program as *mut c_void),
|
||||
Err(err) => RegorusResult::err_with_message(
|
||||
RegorusStatus::CompilationFailed,
|
||||
format!("RVM compilation failed: {err}"),
|
||||
),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Compile an RVM program from data/modules and entry points.
|
||||
///
|
||||
/// * `data_json` - JSON string containing static data for policy evaluation
|
||||
/// * `modules` - Array of policy modules to compile
|
||||
/// * `modules_len` - Number of modules in the array
|
||||
/// * `entry_points` - Array of entry point rule paths
|
||||
/// * `entry_points_len` - Number of entry points
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_program_compile_from_modules(
|
||||
data_json: *const c_char,
|
||||
modules: *const RegorusPolicyModule,
|
||||
modules_len: usize,
|
||||
entry_points: *const *const c_char,
|
||||
entry_points_len: usize,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<*mut RegorusProgram> {
|
||||
if entry_points_len == 0 {
|
||||
return Err(anyhow!("entry_points must contain at least one entry"));
|
||||
}
|
||||
|
||||
let data_str = from_c_str(data_json)?;
|
||||
let data = Value::from_json_str(&data_str)?;
|
||||
let policy_modules = convert_c_modules_to_rust(modules, modules_len)?;
|
||||
|
||||
let entry_points_vec = convert_c_entry_points(entry_points, entry_points_len)?;
|
||||
let entry_points_ref: Vec<&str> = entry_points_vec.iter().map(|s| s.as_str()).collect();
|
||||
|
||||
let entry_rule = entry_points_ref
|
||||
.first()
|
||||
.ok_or_else(|| anyhow!("entry_points must contain at least one entry"))?;
|
||||
|
||||
let compiled_policy = regorus::compile_policy_with_entrypoint(
|
||||
data,
|
||||
&policy_modules,
|
||||
(*entry_rule).into(),
|
||||
)?;
|
||||
|
||||
let program = Compiler::compile_from_policy(&compiled_policy, &entry_points_ref)?;
|
||||
Ok(Box::into_raw(Box::new(RegorusProgram { program })))
|
||||
}();
|
||||
|
||||
match output {
|
||||
Ok(program) => RegorusResult::ok_pointer(program as *mut c_void),
|
||||
Err(err) => RegorusResult::err_with_message(
|
||||
RegorusStatus::CompilationFailed,
|
||||
format!("RVM compilation failed: {err}"),
|
||||
),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new, empty RVM program.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_program_new() -> *mut RegorusProgram {
|
||||
let program = Program::new();
|
||||
Box::into_raw(Box::new(RegorusProgram {
|
||||
program: Arc::new(program),
|
||||
}))
|
||||
}
|
||||
|
||||
/// Serialize a program to the binary RVM format.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_program_serialize_binary(program: *mut RegorusProgram) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<*mut RegorusBuffer> {
|
||||
let program = &to_ref(program)?.program;
|
||||
let bytes = program.serialize_binary().map_err(|e| anyhow!(e))?;
|
||||
Ok(RegorusBuffer::from_vec(bytes))
|
||||
}();
|
||||
|
||||
match output {
|
||||
Ok(buffer) => RegorusResult::ok_pointer(buffer as *mut c_void),
|
||||
Err(err) => RegorusResult::err_with_message(RegorusStatus::Error, format!("{err}")),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Deserialize a program from the binary RVM format.
|
||||
///
|
||||
/// Returns a `RegorusProgram` handle and sets `is_partial` to true when the
|
||||
/// program requires recompilation.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_program_deserialize_binary(
|
||||
data: *const u8,
|
||||
len: usize,
|
||||
is_partial: *mut bool,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<(*mut RegorusProgram, bool)> {
|
||||
if data.is_null() && len > 0 {
|
||||
return Err(anyhow!("null data pointer"));
|
||||
}
|
||||
let data = unsafe { core::slice::from_raw_parts(data, len) };
|
||||
let (program, partial) =
|
||||
match Program::deserialize_binary(data).map_err(|e| anyhow!(e))? {
|
||||
DeserializationResult::Complete(program) => (program, false),
|
||||
DeserializationResult::Partial(program) => (program, true),
|
||||
};
|
||||
Ok((
|
||||
Box::into_raw(Box::new(RegorusProgram {
|
||||
program: Arc::new(program),
|
||||
})),
|
||||
partial,
|
||||
))
|
||||
}();
|
||||
|
||||
match output {
|
||||
Ok((program, partial)) => {
|
||||
if !is_partial.is_null() {
|
||||
unsafe {
|
||||
*is_partial = partial;
|
||||
}
|
||||
}
|
||||
RegorusResult::ok_pointer(program as *mut c_void)
|
||||
}
|
||||
Err(err) => {
|
||||
RegorusResult::err_with_message(RegorusStatus::InvalidDataFormat, err.to_string())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate a default assembly listing for the program.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_program_generate_listing(program: *mut RegorusProgram) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let program = &to_ref(program)?.program;
|
||||
Ok(generate_assembly_listing(
|
||||
program,
|
||||
&AssemblyListingConfig::default(),
|
||||
))
|
||||
}();
|
||||
|
||||
match output {
|
||||
Ok(listing) => RegorusResult::ok_string(listing),
|
||||
Err(err) => RegorusResult::err_with_message(RegorusStatus::Error, format!("{err}")),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate a tabular assembly listing for the program.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_program_generate_tabular_listing(
|
||||
program: *mut RegorusProgram,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let program = &to_ref(program)?.program;
|
||||
Ok(generate_tabular_assembly_listing(
|
||||
program,
|
||||
&AssemblyListingConfig::default(),
|
||||
))
|
||||
}();
|
||||
|
||||
match output {
|
||||
Ok(listing) => RegorusResult::ok_string(listing),
|
||||
Err(err) => RegorusResult::err_with_message(RegorusStatus::Error, format!("{err}")),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Construct a new RVM instance.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_rvm_new() -> *mut RegorusRvm {
|
||||
Box::into_raw(Box::new(RegorusRvm::new(RegoVM::new())))
|
||||
}
|
||||
|
||||
/// Construct a new RVM instance with a compiled policy for default rule evaluation.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_rvm_new_with_policy(
|
||||
compiled_policy: *mut RegorusCompiledPolicy,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<*mut RegorusRvm> {
|
||||
let policy = to_ref(compiled_policy)?.compiled_policy.clone();
|
||||
Ok(Box::into_raw(Box::new(RegorusRvm::new(
|
||||
RegoVM::new_with_policy(policy),
|
||||
))))
|
||||
}();
|
||||
|
||||
match output {
|
||||
Ok(vm) => RegorusResult::ok_pointer(vm as *mut c_void),
|
||||
Err(err) => RegorusResult::err_with_message(RegorusStatus::Error, err.to_string()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Load a program into the RVM.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_rvm_load_program(
|
||||
vm: *mut RegorusRvm,
|
||||
program: *mut RegorusProgram,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let vm = to_ref(vm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
let program = to_ref(program)?.program.clone();
|
||||
guard.load_program(program);
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Set the VM data document from JSON.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_rvm_set_data(vm: *mut RegorusRvm, data: *const c_char) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let vm = to_ref(vm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
let data_value = Value::from_json_str(&from_c_str(data)?)?;
|
||||
guard.set_data(data_value)?;
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Set the VM input document from JSON.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_rvm_set_input(
|
||||
vm: *mut RegorusRvm,
|
||||
input: *const c_char,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let vm = to_ref(vm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
let input_value = Value::from_json_str(&from_c_str(input)?)?;
|
||||
guard.set_input(input_value);
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Set the maximum number of instructions that can execute.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_rvm_set_max_instructions(
|
||||
vm: *mut RegorusRvm,
|
||||
max_instructions: usize,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let vm = to_ref(vm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
guard.set_max_instructions(max_instructions);
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Configure strict builtin error behavior.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_rvm_set_strict_builtin_errors(
|
||||
vm: *mut RegorusRvm,
|
||||
strict: bool,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let vm = to_ref(vm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
guard.set_strict_builtin_errors(strict);
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Configure the execution mode (0 = run-to-completion, 1 = suspendable).
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_rvm_set_execution_mode(vm: *mut RegorusRvm, mode: u8) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let vm = to_ref(vm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
let mode = match mode {
|
||||
0 => ExecutionMode::RunToCompletion,
|
||||
1 => ExecutionMode::Suspendable,
|
||||
_ => return Err(anyhow!("invalid execution mode: {mode}")),
|
||||
};
|
||||
guard.set_execution_mode(mode);
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Enable or disable step mode when running suspendable execution.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_rvm_set_step_mode(vm: *mut RegorusRvm, enabled: bool) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let vm = to_ref(vm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
guard.set_step_mode(enabled);
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Configure the per-VM execution timer override.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_rvm_set_execution_timer_config(
|
||||
vm: *mut RegorusRvm,
|
||||
has_config: bool,
|
||||
config: RegorusExecutionTimerConfig,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let vm = to_ref(vm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
if has_config {
|
||||
guard.set_execution_timer_config(Some(config.to_execution_timer_config()?));
|
||||
} else {
|
||||
guard.set_execution_timer_config(None);
|
||||
}
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Execute the program's main entry point.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_rvm_execute(vm: *mut RegorusRvm) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let vm = to_ref(vm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
let result = guard.execute()?;
|
||||
result.to_json_str()
|
||||
}();
|
||||
|
||||
match output {
|
||||
Ok(json) => RegorusResult::ok_string(json),
|
||||
Err(err) => RegorusResult::err_with_message(RegorusStatus::Error, err.to_string()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Execute a named entry point.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_rvm_execute_entry_point_by_name(
|
||||
vm: *mut RegorusRvm,
|
||||
entry_point: *const c_char,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let vm = to_ref(vm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
let name = from_c_str(entry_point)?;
|
||||
let result = guard.execute_entry_point_by_name(&name)?;
|
||||
result.to_json_str()
|
||||
}();
|
||||
|
||||
match output {
|
||||
Ok(json) => RegorusResult::ok_string(json),
|
||||
Err(err) => RegorusResult::err_with_message(RegorusStatus::Error, err.to_string()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Execute an entry point by index.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_rvm_execute_entry_point_by_index(
|
||||
vm: *mut RegorusRvm,
|
||||
index: usize,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let vm = to_ref(vm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
let result = guard.execute_entry_point_by_index(index)?;
|
||||
result.to_json_str()
|
||||
}();
|
||||
|
||||
match output {
|
||||
Ok(json) => RegorusResult::ok_string(json),
|
||||
Err(err) => RegorusResult::err_with_message(RegorusStatus::Error, err.to_string()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Resume execution for suspendable runs.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_rvm_resume(
|
||||
vm: *mut RegorusRvm,
|
||||
resume_value_json: *const c_char,
|
||||
has_value: bool,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let vm = to_ref(vm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
let value = if has_value {
|
||||
Some(Value::from_json_str(&from_c_str(resume_value_json)?)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let result = guard.resume(value)?;
|
||||
result.to_json_str()
|
||||
}();
|
||||
|
||||
match output {
|
||||
Ok(json) => RegorusResult::ok_string(json),
|
||||
Err(err) => RegorusResult::err_with_message(RegorusStatus::Error, err.to_string()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the current execution state of the VM.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_rvm_get_execution_state(vm: *mut RegorusRvm) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let vm = to_ref(vm)?;
|
||||
let guard = vm.try_read()?;
|
||||
let state: ExecutionState = guard.execution_state().clone();
|
||||
Ok(format!("{:?}", state))
|
||||
}();
|
||||
|
||||
match output {
|
||||
Ok(json) => RegorusResult::ok_string(json),
|
||||
Err(err) => RegorusResult::err_with_message(RegorusStatus::Error, err.to_string()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn convert_c_entry_points(
|
||||
entry_points: *const *const c_char,
|
||||
entry_points_len: usize,
|
||||
) -> Result<Vec<String>> {
|
||||
if entry_points.is_null() && entry_points_len > 0 {
|
||||
return Err(anyhow!("null entry_points pointer"));
|
||||
}
|
||||
|
||||
let mut entry_points_vec = Vec::with_capacity(entry_points_len);
|
||||
for i in 0..entry_points_len {
|
||||
unsafe {
|
||||
let entry_ptr = entry_points.add(i);
|
||||
if entry_ptr.is_null() {
|
||||
return Err(anyhow!("null entry point at index {i}"));
|
||||
}
|
||||
let entry = from_c_str(*entry_ptr)?;
|
||||
entry_points_vec.push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(entry_points_vec)
|
||||
}
|
||||
|
||||
fn convert_c_modules_to_rust(
|
||||
modules: *const RegorusPolicyModule,
|
||||
modules_len: usize,
|
||||
) -> Result<Vec<PolicyModule>> {
|
||||
if modules.is_null() && modules_len > 0 {
|
||||
return Err(anyhow!("null modules pointer"));
|
||||
}
|
||||
|
||||
let mut policy_modules = Vec::with_capacity(modules_len);
|
||||
|
||||
for i in 0..modules_len {
|
||||
unsafe {
|
||||
let module = modules.add(i);
|
||||
if module.is_null() {
|
||||
return Err(anyhow!("null module at index {i}"));
|
||||
}
|
||||
|
||||
let module_ref = &*module;
|
||||
|
||||
let id = from_c_str(module_ref.id)
|
||||
.map_err(|e| anyhow!("invalid module id at index {i}: {e}"))?;
|
||||
let content = from_c_str(module_ref.content)
|
||||
.map_err(|e| anyhow!("invalid module content at index {i}: {e}"))?;
|
||||
|
||||
policy_modules.push(PolicyModule {
|
||||
id: id.into(),
|
||||
content: content.into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(policy_modules)
|
||||
}
|
||||
@@ -100,4 +100,138 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("%s\n", output)
|
||||
|
||||
// RVM regular example (compile, serialize, execute)
|
||||
const regularPolicy = `
|
||||
package demo
|
||||
import rego.v1
|
||||
|
||||
default allow := false
|
||||
|
||||
allow if {
|
||||
input.user == "alice"
|
||||
input.active == true
|
||||
}
|
||||
`
|
||||
const regularInput = `{"user":"alice","active":true}`
|
||||
|
||||
regularModules := []regorus.PolicyModule{{Id: "demo.rego", Content: regularPolicy}}
|
||||
regularEntryPoints := []string{"data.demo.allow"}
|
||||
regularProgram, err := regorus.CompileProgramFromModules("{}", regularModules, regularEntryPoints)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer regularProgram.Close()
|
||||
|
||||
listing, err := regularProgram.GenerateListing()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("RVM listing:\n%s\n", listing)
|
||||
|
||||
binary, err := regularProgram.SerializeBinary()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
regularProgram.Close()
|
||||
|
||||
rehydrated, isPartial, err := regorus.DeserializeProgram(binary)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if isPartial {
|
||||
fmt.Fprintf(os.Stderr, "error: program marked partial\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
defer rehydrated.Close()
|
||||
|
||||
regularVm, err := regorus.NewRvm()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer regularVm.Close()
|
||||
|
||||
if err := regularVm.LoadProgram(rehydrated); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := regularVm.SetInputJson(regularInput); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
regularResult, err := regularVm.Execute()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("RVM regular result: %s\n", regularResult)
|
||||
|
||||
// RVM HostAwait example
|
||||
const rvmPolicy = `
|
||||
package demo
|
||||
import rego.v1
|
||||
|
||||
default allow := false
|
||||
|
||||
allow if {
|
||||
input.account.active == true
|
||||
details := __builtin_host_await(input.account.id, "account")
|
||||
details.tier == "gold"
|
||||
}
|
||||
`
|
||||
const rvmInput = `{"account":{"id":"acct-1","active":true}}`
|
||||
|
||||
modules := []regorus.PolicyModule{{Id: "demo.rego", Content: rvmPolicy}}
|
||||
entryPoints := []string{"data.demo.allow"}
|
||||
program, err := regorus.CompileProgramFromModules("{}", modules, entryPoints)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer program.Close()
|
||||
|
||||
vm, err := regorus.NewRvm()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer vm.Close()
|
||||
|
||||
if err := vm.SetExecutionMode(1); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := vm.LoadProgram(program); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := vm.SetInputJson(rvmInput); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if _, err := vm.Execute(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
state, err := vm.GetExecutionState()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("HostAwait state: %s\n", state)
|
||||
|
||||
result, err := vm.Resume(`{"tier":"gold"}`, true)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("HostAwait result: %s\n", result)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package regorus
|
||||
|
||||
// #cgo LDFLAGS: -L ../../../ffi/target/release -lregorus_ffi
|
||||
// #cgo LDFLAGS: -L ../../../ffi/target/release -L ../../../ffi/target/debug -lregorus_ffi
|
||||
// #include "../../../ffi/regorus.h"
|
||||
import "C"
|
||||
import (
|
||||
|
||||
283
bindings/go/pkg/regorus/rvm.go
Normal file
283
bindings/go/pkg/regorus/rvm.go
Normal file
@@ -0,0 +1,283 @@
|
||||
package regorus
|
||||
|
||||
// #cgo LDFLAGS: -L ../../../ffi/target/release -L ../../../ffi/target/debug -lregorus_ffi
|
||||
// #include "../../../ffi/regorus.h"
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type PolicyModule struct {
|
||||
Id string
|
||||
Content string
|
||||
}
|
||||
|
||||
type Program struct {
|
||||
p *C.RegorusProgram
|
||||
}
|
||||
|
||||
type Rvm struct {
|
||||
vm *C.RegorusRvm
|
||||
}
|
||||
|
||||
type Buffer struct {
|
||||
b *C.RegorusBuffer
|
||||
}
|
||||
|
||||
func (b *Buffer) Close() {
|
||||
if b != nil && b.b != nil {
|
||||
C.regorus_buffer_drop(b.b)
|
||||
b.b = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Buffer) Bytes() []byte {
|
||||
if b == nil || b.b == nil || b.b.data == nil || b.b.len == 0 {
|
||||
return nil
|
||||
}
|
||||
return C.GoBytes(unsafe.Pointer(b.b.data), C.int(b.b.len))
|
||||
}
|
||||
|
||||
func (p *Program) Close() {
|
||||
if p != nil && p.p != nil {
|
||||
C.regorus_program_drop(p.p)
|
||||
p.p = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Program) SerializeBinary() ([]byte, error) {
|
||||
result := C.regorus_program_serialize_binary(p.p)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return nil, fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
buffer := &Buffer{b: (*C.RegorusBuffer)(result.pointer_value)}
|
||||
defer buffer.Close()
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
func (p *Program) GenerateListing() (string, error) {
|
||||
result := C.regorus_program_generate_listing(p.p)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return "", fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return C.GoString(result.output), nil
|
||||
}
|
||||
|
||||
func (p *Program) GenerateTabularListing() (string, error) {
|
||||
result := C.regorus_program_generate_tabular_listing(p.p)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return "", fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return C.GoString(result.output), nil
|
||||
}
|
||||
|
||||
func DeserializeProgram(data []byte) (*Program, bool, error) {
|
||||
if len(data) == 0 {
|
||||
return nil, false, fmt.Errorf("empty program data")
|
||||
}
|
||||
var isPartial C.bool
|
||||
result := C.regorus_program_deserialize_binary((*C.uchar)(unsafe.Pointer(&data[0])), C.ulong(len(data)), (*C.bool)(unsafe.Pointer(&isPartial)))
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return nil, false, fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return &Program{p: (*C.RegorusProgram)(result.pointer_value)}, bool(isPartial), nil
|
||||
}
|
||||
|
||||
func CompileProgramFromModules(data string, modules []PolicyModule, entryPoints []string) (*Program, error) {
|
||||
dataC := C.CString(data)
|
||||
defer C.free(unsafe.Pointer(dataC))
|
||||
|
||||
cModules := make([]C.RegorusPolicyModule, len(modules))
|
||||
moduleIdPtrs := make([]*C.char, len(modules))
|
||||
moduleContentPtrs := make([]*C.char, len(modules))
|
||||
for i, module := range modules {
|
||||
idC := C.CString(module.Id)
|
||||
contentC := C.CString(module.Content)
|
||||
moduleIdPtrs[i] = idC
|
||||
moduleContentPtrs[i] = contentC
|
||||
cModules[i].id = idC
|
||||
cModules[i].content = contentC
|
||||
}
|
||||
defer func() {
|
||||
for i := range moduleIdPtrs {
|
||||
if moduleIdPtrs[i] != nil {
|
||||
C.free(unsafe.Pointer(moduleIdPtrs[i]))
|
||||
}
|
||||
if moduleContentPtrs[i] != nil {
|
||||
C.free(unsafe.Pointer(moduleContentPtrs[i]))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
entryPtrs := make([]*C.char, len(entryPoints))
|
||||
for i, entry := range entryPoints {
|
||||
entryPtrs[i] = C.CString(entry)
|
||||
}
|
||||
defer func() {
|
||||
for _, ptr := range entryPtrs {
|
||||
C.free(unsafe.Pointer(ptr))
|
||||
}
|
||||
}()
|
||||
|
||||
var modulesPtr *C.RegorusPolicyModule
|
||||
if len(cModules) > 0 {
|
||||
modulesPtr = (*C.RegorusPolicyModule)(unsafe.Pointer(&cModules[0]))
|
||||
}
|
||||
var entryPtr **C.char
|
||||
if len(entryPtrs) > 0 {
|
||||
entryPtr = (**C.char)(unsafe.Pointer(&entryPtrs[0]))
|
||||
}
|
||||
|
||||
result := C.regorus_program_compile_from_modules(
|
||||
dataC,
|
||||
modulesPtr,
|
||||
C.ulong(len(cModules)),
|
||||
entryPtr,
|
||||
C.ulong(len(entryPtrs)),
|
||||
)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return nil, fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return &Program{p: (*C.RegorusProgram)(result.pointer_value)}, nil
|
||||
}
|
||||
|
||||
func CompileProgramFromEngine(engine *Engine, entryPoints []string) (*Program, error) {
|
||||
entryPtrs := make([]*C.char, len(entryPoints))
|
||||
for i, entry := range entryPoints {
|
||||
entryPtrs[i] = C.CString(entry)
|
||||
}
|
||||
defer func() {
|
||||
for _, ptr := range entryPtrs {
|
||||
C.free(unsafe.Pointer(ptr))
|
||||
}
|
||||
}()
|
||||
|
||||
var entryPtr **C.char
|
||||
if len(entryPtrs) > 0 {
|
||||
entryPtr = (**C.char)(unsafe.Pointer(&entryPtrs[0]))
|
||||
}
|
||||
|
||||
result := C.regorus_engine_compile_program_with_entrypoints(
|
||||
engine.e,
|
||||
entryPtr,
|
||||
C.ulong(len(entryPtrs)),
|
||||
)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return nil, fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return &Program{p: (*C.RegorusProgram)(result.pointer_value)}, nil
|
||||
}
|
||||
|
||||
func NewRvm() (*Rvm, error) {
|
||||
vm := C.regorus_rvm_new()
|
||||
if vm == nil {
|
||||
return nil, fmt.Errorf("failed to create RVM")
|
||||
}
|
||||
return &Rvm{vm: vm}, nil
|
||||
}
|
||||
|
||||
func (r *Rvm) Close() {
|
||||
if r != nil && r.vm != nil {
|
||||
C.regorus_rvm_drop(r.vm)
|
||||
r.vm = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Rvm) LoadProgram(program *Program) error {
|
||||
result := C.regorus_rvm_load_program(r.vm, program.p)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Rvm) SetDataJson(data string) error {
|
||||
dataC := C.CString(data)
|
||||
defer C.free(unsafe.Pointer(dataC))
|
||||
result := C.regorus_rvm_set_data(r.vm, dataC)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Rvm) SetInputJson(input string) error {
|
||||
inputC := C.CString(input)
|
||||
defer C.free(unsafe.Pointer(inputC))
|
||||
result := C.regorus_rvm_set_input(r.vm, inputC)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Rvm) SetExecutionMode(mode byte) error {
|
||||
result := C.regorus_rvm_set_execution_mode(r.vm, C.uchar(mode))
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Rvm) Execute() (string, error) {
|
||||
result := C.regorus_rvm_execute(r.vm)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return "", fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return C.GoString(result.output), nil
|
||||
}
|
||||
|
||||
func (r *Rvm) ExecuteEntryPoint(name string) (string, error) {
|
||||
nameC := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(nameC))
|
||||
result := C.regorus_rvm_execute_entry_point_by_name(r.vm, nameC)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return "", fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return C.GoString(result.output), nil
|
||||
}
|
||||
|
||||
func (r *Rvm) ExecuteEntryPointIndex(index uint64) (string, error) {
|
||||
result := C.regorus_rvm_execute_entry_point_by_index(r.vm, C.ulong(index))
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return "", fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return C.GoString(result.output), nil
|
||||
}
|
||||
|
||||
func (r *Rvm) Resume(resumeValue string, hasValue bool) (string, error) {
|
||||
var valueC *C.char
|
||||
if hasValue {
|
||||
valueC = C.CString(resumeValue)
|
||||
defer C.free(unsafe.Pointer(valueC))
|
||||
}
|
||||
result := C.regorus_rvm_resume(r.vm, valueC, C.bool(hasValue))
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return "", fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return C.GoString(result.output), nil
|
||||
}
|
||||
|
||||
func (r *Rvm) GetExecutionState() (string, error) {
|
||||
result := C.regorus_rvm_get_execution_state(r.vm)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return "", fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return C.GoString(result.output), nil
|
||||
}
|
||||
128
bindings/go/pkg/regorus/rvm_test.go
Normal file
128
bindings/go/pkg/regorus/rvm_test.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package regorus
|
||||
|
||||
import "testing"
|
||||
|
||||
const rvmPolicy = `
|
||||
package demo
|
||||
import rego.v1
|
||||
|
||||
default allow := false
|
||||
|
||||
allow if {
|
||||
input.account.active == true
|
||||
details := __builtin_host_await(input.account.id, "account")
|
||||
details.tier == "gold"
|
||||
}
|
||||
`
|
||||
|
||||
const rvmInput = `{"account":{"id":"acct-1","active":true}}`
|
||||
|
||||
const rvmRegularPolicy = `
|
||||
package demo
|
||||
import rego.v1
|
||||
|
||||
default allow := false
|
||||
|
||||
allow if {
|
||||
input.user == "alice"
|
||||
input.active == true
|
||||
}
|
||||
`
|
||||
|
||||
const rvmRegularInput = `{"user":"alice","active":true}`
|
||||
|
||||
func TestRvmProgramCompileAndExecute(t *testing.T) {
|
||||
modules := []PolicyModule{{Id: "demo.rego", Content: rvmRegularPolicy}}
|
||||
entryPoints := []string{"data.demo.allow"}
|
||||
program, err := CompileProgramFromModules("{}", modules, entryPoints)
|
||||
if err != nil {
|
||||
t.Fatalf("compile program: %v", err)
|
||||
}
|
||||
defer program.Close()
|
||||
|
||||
listing, err := program.GenerateListing()
|
||||
if err != nil || listing == "" {
|
||||
t.Fatalf("listing failed: %v", err)
|
||||
}
|
||||
|
||||
binary, err := program.SerializeBinary()
|
||||
if err != nil {
|
||||
t.Fatalf("serialize program: %v", err)
|
||||
}
|
||||
|
||||
rehydrated, isPartial, err := DeserializeProgram(binary)
|
||||
if err != nil {
|
||||
t.Fatalf("deserialize program: %v", err)
|
||||
}
|
||||
if isPartial {
|
||||
t.Fatalf("deserialized program marked partial")
|
||||
}
|
||||
defer rehydrated.Close()
|
||||
|
||||
vm, err := NewRvm()
|
||||
if err != nil {
|
||||
t.Fatalf("new vm: %v", err)
|
||||
}
|
||||
defer vm.Close()
|
||||
|
||||
if err := vm.LoadProgram(rehydrated); err != nil {
|
||||
t.Fatalf("load program: %v", err)
|
||||
}
|
||||
if err := vm.SetInputJson(rvmRegularInput); err != nil {
|
||||
t.Fatalf("set input: %v", err)
|
||||
}
|
||||
|
||||
result, err := vm.Execute()
|
||||
if err != nil {
|
||||
t.Fatalf("execute: %v", err)
|
||||
}
|
||||
if result != "true" {
|
||||
t.Fatalf("expected allow=true, got %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRvmHostAwaitSuspendResume(t *testing.T) {
|
||||
modules := []PolicyModule{{Id: "host_await.rego", Content: rvmPolicy}}
|
||||
entryPoints := []string{"data.demo.allow"}
|
||||
program, err := CompileProgramFromModules("{}", modules, entryPoints)
|
||||
if err != nil {
|
||||
t.Fatalf("compile program: %v", err)
|
||||
}
|
||||
defer program.Close()
|
||||
|
||||
vm, err := NewRvm()
|
||||
if err != nil {
|
||||
t.Fatalf("new vm: %v", err)
|
||||
}
|
||||
defer vm.Close()
|
||||
|
||||
if err := vm.SetExecutionMode(1); err != nil {
|
||||
t.Fatalf("set execution mode: %v", err)
|
||||
}
|
||||
if err := vm.LoadProgram(program); err != nil {
|
||||
t.Fatalf("load program: %v", err)
|
||||
}
|
||||
if err := vm.SetInputJson(rvmInput); err != nil {
|
||||
t.Fatalf("set input: %v", err)
|
||||
}
|
||||
|
||||
if _, err := vm.Execute(); err != nil {
|
||||
t.Fatalf("execute in suspendable mode failed: %v", err)
|
||||
}
|
||||
|
||||
state, err := vm.GetExecutionState()
|
||||
if err != nil {
|
||||
t.Fatalf("get execution state: %v", err)
|
||||
}
|
||||
if state == "" {
|
||||
t.Fatalf("expected non-empty execution state")
|
||||
}
|
||||
|
||||
result, err := vm.Resume(`{"tier":"gold"}`, true)
|
||||
if err != nil {
|
||||
t.Fatalf("resume: %v", err)
|
||||
}
|
||||
if result != "true" {
|
||||
t.Fatalf("expected allow=true, got %s", result)
|
||||
}
|
||||
}
|
||||
BIN
bindings/go/regorus_test
Executable file
BIN
bindings/go/regorus_test
Executable file
Binary file not shown.
40
bindings/java/Cargo.lock
generated
40
bindings/java/Cargo.lock
generated
@@ -52,6 +52,16 @@ version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"unty",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.8.0"
|
||||
@@ -109,9 +119,9 @@ checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.54"
|
||||
version = "1.2.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583"
|
||||
checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
@@ -213,9 +223,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.8"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
|
||||
[[package]]
|
||||
name = "fluent-uri"
|
||||
@@ -279,9 +289,9 @@ checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.64"
|
||||
version = "0.1.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
|
||||
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
@@ -411,6 +421,8 @@ checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -817,10 +829,12 @@ name = "regorus"
|
||||
version = "0.9.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
"chrono",
|
||||
"chrono-tz",
|
||||
"data-encoding",
|
||||
"globset",
|
||||
"indexmap",
|
||||
"ipnet",
|
||||
"jsonschema",
|
||||
"lazy_static",
|
||||
@@ -1067,6 +1081,12 @@ version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
||||
|
||||
[[package]]
|
||||
name = "unty"
|
||||
version = "0.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.8"
|
||||
@@ -1364,18 +1384,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.34"
|
||||
version = "0.8.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71ddd76bcebeed25db614f82bf31a9f4222d3fbba300e6fb6c00afa26cbd4d9d"
|
||||
checksum = "dafd85c832c1b68bbb4ec0c72c7f6f4fc5179627d2bc7c26b30e4c0cc11e76cc"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.34"
|
||||
version = "0.8.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8187381b52e32220d50b255276aa16a084ec0a9017a0ca2152a1f55c539758d"
|
||||
checksum = "7cb7e4e8436d9db52fbd6625dbf2f45243ab84994a72882ec8227b99e72b439a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -21,4 +21,4 @@ ast = ["regorus/ast"]
|
||||
anyhow = "1.0"
|
||||
serde_json = "1.0.112"
|
||||
jni = "0.21.1"
|
||||
regorus = { path = "../..", default-features = false, features = ["arc"] }
|
||||
regorus = { path = "../..", default-features = false, features = ["arc", "rvm"] }
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
// Licensed under the MIT License.
|
||||
|
||||
import com.microsoft.regorus.Engine;
|
||||
import com.microsoft.regorus.PolicyModule;
|
||||
import com.microsoft.regorus.Program;
|
||||
import com.microsoft.regorus.Rvm;
|
||||
|
||||
public class Test {
|
||||
|
||||
@@ -44,5 +47,70 @@ public class Test {
|
||||
"package world\nx { true }"
|
||||
);
|
||||
}
|
||||
|
||||
String regularPolicy = String.join("\n",
|
||||
"package demo",
|
||||
"import rego.v1",
|
||||
"",
|
||||
"default allow := false",
|
||||
"",
|
||||
"allow if {",
|
||||
" input.user == \"alice\"",
|
||||
" input.active == true",
|
||||
"}"
|
||||
);
|
||||
String regularInput = "{\"user\":\"alice\",\"active\":true}";
|
||||
|
||||
{
|
||||
PolicyModule module = new PolicyModule("demo.rego", regularPolicy);
|
||||
Program program = Program.compileFromModules("{}", new PolicyModule[]{module}, new String[]{"data.demo.allow"});
|
||||
System.out.println("RVM listing:\n" + program.generateListing());
|
||||
|
||||
byte[] binary = program.serializeBinary();
|
||||
program.close();
|
||||
|
||||
boolean[] isPartial = new boolean[1];
|
||||
Program rehydrated = Program.deserializeBinary(binary, isPartial);
|
||||
if (isPartial[0]) {
|
||||
throw new IllegalStateException("Deserialized program marked partial");
|
||||
}
|
||||
|
||||
try (Rvm vm = new Rvm()) {
|
||||
vm.loadProgram(rehydrated);
|
||||
vm.setInputJson(regularInput);
|
||||
String result = vm.execute();
|
||||
System.out.println("RVM regular result: " + result);
|
||||
}
|
||||
rehydrated.close();
|
||||
}
|
||||
|
||||
String awaitPolicy = String.join("\n",
|
||||
"package demo",
|
||||
"import rego.v1",
|
||||
"",
|
||||
"default allow := false",
|
||||
"",
|
||||
"allow if {",
|
||||
" input.account.active == true",
|
||||
" details := __builtin_host_await(input.account.id, \"account\")",
|
||||
" details.tier == \"gold\"",
|
||||
"}"
|
||||
);
|
||||
String awaitInput = "{\"account\":{\"id\":\"acct-1\",\"active\":true}}";
|
||||
|
||||
{
|
||||
PolicyModule module = new PolicyModule("await.rego", awaitPolicy);
|
||||
Program program = Program.compileFromModules("{}", new PolicyModule[]{module}, new String[]{"data.demo.allow"});
|
||||
try (Rvm vm = new Rvm()) {
|
||||
vm.setExecutionMode((byte) 1);
|
||||
vm.loadProgram(program);
|
||||
vm.setInputJson(awaitInput);
|
||||
vm.execute();
|
||||
System.out.println("HostAwait state: " + vm.getExecutionState());
|
||||
String resumed = vm.resume("{\"tier\":\"gold\"}");
|
||||
System.out.println("HostAwait result: " + resumed);
|
||||
}
|
||||
program.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,17 @@
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use anyhow::Result;
|
||||
use jni::objects::{JClass, JObject, JString};
|
||||
use jni::sys::{jlong, jstring};
|
||||
use jni::objects::{JBooleanArray, JByteArray, JClass, JObject, JObjectArray, JString};
|
||||
use jni::sys::{jboolean, jbooleanArray, jbyteArray, jlong, jobjectArray, jstring};
|
||||
use jni::JNIEnv;
|
||||
|
||||
use regorus::{Engine, Value};
|
||||
use regorus::languages::rego::compiler::Compiler;
|
||||
use regorus::rvm::program::{
|
||||
generate_assembly_listing, AssemblyListingConfig, DeserializationResult, Program as RvmProgram,
|
||||
};
|
||||
use regorus::rvm::vm::{ExecutionMode, RegoVM};
|
||||
use regorus::{compile_policy_with_entrypoint, Engine, PolicyModule, Rc, Value};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeNewEngine(
|
||||
@@ -370,6 +376,336 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeDestroyEngine(
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Program_nativeCompileFromModules(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
data_json: JString,
|
||||
module_ids: jobjectArray,
|
||||
module_contents: jobjectArray,
|
||||
entry_points: jobjectArray,
|
||||
) -> jlong {
|
||||
let res = throw_err(env, |env| {
|
||||
let data_json: String = env.get_string(&data_json)?.into();
|
||||
let data = Value::from_json_str(&data_json)?;
|
||||
|
||||
let ids = get_string_array(env, module_ids)?;
|
||||
let contents = get_string_array(env, module_contents)?;
|
||||
if ids.len() != contents.len() {
|
||||
return Err(anyhow::anyhow!("module id/content length mismatch"));
|
||||
}
|
||||
|
||||
let mut modules = Vec::with_capacity(ids.len());
|
||||
for (id, content) in ids.into_iter().zip(contents.into_iter()) {
|
||||
modules.push(PolicyModule {
|
||||
id: Rc::from(id.as_str()),
|
||||
content: Rc::from(content.as_str()),
|
||||
});
|
||||
}
|
||||
|
||||
let entry_points_vec = get_string_array(env, entry_points)?;
|
||||
if entry_points_vec.is_empty() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"entry_points must contain at least one entry"
|
||||
));
|
||||
}
|
||||
let entry_points_ref: Vec<&str> = entry_points_vec.iter().map(|s| s.as_str()).collect();
|
||||
let entry_rule = entry_points_ref[0];
|
||||
|
||||
let compiled = compile_policy_with_entrypoint(data, &modules, Rc::from(entry_rule))?;
|
||||
let program = Compiler::compile_from_policy(&compiled, &entry_points_ref)?;
|
||||
Ok(Box::into_raw(Box::new(program)) as jlong)
|
||||
});
|
||||
|
||||
res.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Program_nativeCompileFromEngine(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
engine_ptr: jlong,
|
||||
entry_points: jobjectArray,
|
||||
) -> jlong {
|
||||
let res = throw_err(env, |env| {
|
||||
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
|
||||
let entry_points_vec = get_string_array(env, entry_points)?;
|
||||
if entry_points_vec.is_empty() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"entry_points must contain at least one entry"
|
||||
));
|
||||
}
|
||||
let entry_points_ref: Vec<&str> = entry_points_vec.iter().map(|s| s.as_str()).collect();
|
||||
let entry_rule = Rc::from(entry_points_ref[0]);
|
||||
let compiled = engine.compile_with_entrypoint(&entry_rule)?;
|
||||
let program = Compiler::compile_from_policy(&compiled, &entry_points_ref)?;
|
||||
Ok(Box::into_raw(Box::new(program)) as jlong)
|
||||
});
|
||||
|
||||
res.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Program_nativeGenerateListing(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
program_ptr: jlong,
|
||||
) -> jstring {
|
||||
let res = throw_err(env, |env| {
|
||||
let program = unsafe { &*(program_ptr as *mut Arc<RvmProgram>) };
|
||||
let listing =
|
||||
generate_assembly_listing(program.as_ref(), &AssemblyListingConfig::default());
|
||||
let output = env.new_string(&listing)?;
|
||||
Ok(output.into_raw())
|
||||
});
|
||||
|
||||
match res {
|
||||
Ok(val) => val,
|
||||
Err(_) => JObject::null().into_raw(),
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Program_nativeSerializeBinary(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
program_ptr: jlong,
|
||||
) -> jbyteArray {
|
||||
let res = throw_err(env, |env| {
|
||||
let program = unsafe { &*(program_ptr as *mut Arc<RvmProgram>) };
|
||||
let bytes = program.serialize_binary().map_err(|e| anyhow::anyhow!(e))?;
|
||||
let array = env.byte_array_from_slice(&bytes)?;
|
||||
Ok(array.into_raw())
|
||||
});
|
||||
|
||||
match res {
|
||||
Ok(val) => val,
|
||||
Err(_) => JObject::null().into_raw(),
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
/// # Safety
|
||||
///
|
||||
/// The `data` and `is_partial` pointers must be valid JNI array references
|
||||
/// for the duration of the call. They must come from the JVM for the current
|
||||
/// thread and not be used after this function returns.
|
||||
pub unsafe extern "system" fn Java_com_microsoft_regorus_Program_nativeDeserializeBinary(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
data: jbyteArray,
|
||||
is_partial: jbooleanArray,
|
||||
) -> jlong {
|
||||
let res = throw_err(env, |env| {
|
||||
if data.is_null() {
|
||||
return Err(anyhow::anyhow!("data must not be null"));
|
||||
}
|
||||
|
||||
let data = unsafe { JByteArray::from_raw(data) };
|
||||
let bytes = env.convert_byte_array(&data)?;
|
||||
let (program, partial) =
|
||||
match RvmProgram::deserialize_binary(&bytes).map_err(|e| anyhow::anyhow!(e))? {
|
||||
DeserializationResult::Complete(program) => (program, false),
|
||||
DeserializationResult::Partial(program) => (program, true),
|
||||
};
|
||||
|
||||
if !is_partial.is_null() {
|
||||
let is_partial = unsafe { JBooleanArray::from_raw(is_partial) };
|
||||
let len = env.get_array_length(&is_partial)?;
|
||||
if len > 0 {
|
||||
let value: [jboolean; 1] = [if partial { 1 } else { 0 }];
|
||||
env.set_boolean_array_region(&is_partial, 0, &value)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Box::into_raw(Box::new(Arc::new(program))) as jlong)
|
||||
});
|
||||
|
||||
res.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Program_nativeDrop(
|
||||
_env: JNIEnv,
|
||||
_class: JClass,
|
||||
program_ptr: jlong,
|
||||
) {
|
||||
unsafe {
|
||||
let _program = Box::from_raw(program_ptr as *mut Arc<RvmProgram>);
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeNew(
|
||||
_env: JNIEnv,
|
||||
_class: JClass,
|
||||
) -> jlong {
|
||||
let vm = RegoVM::new();
|
||||
Box::into_raw(Box::new(vm)) as jlong
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeLoadProgram(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
vm_ptr: jlong,
|
||||
program_ptr: jlong,
|
||||
) {
|
||||
let _ = throw_err(env, |_env| {
|
||||
let vm = unsafe { &mut *(vm_ptr as *mut RegoVM) };
|
||||
let program = unsafe { &*(program_ptr as *mut Arc<RvmProgram>) };
|
||||
vm.load_program(program.clone());
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeSetDataJson(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
vm_ptr: jlong,
|
||||
data_json: JString,
|
||||
) {
|
||||
let _ = throw_err(env, |env| {
|
||||
let vm = unsafe { &mut *(vm_ptr as *mut RegoVM) };
|
||||
let data_json: String = env.get_string(&data_json)?.into();
|
||||
let data = Value::from_json_str(&data_json)?;
|
||||
vm.set_data(data)?;
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeSetInputJson(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
vm_ptr: jlong,
|
||||
input_json: JString,
|
||||
) {
|
||||
let _ = throw_err(env, |env| {
|
||||
let vm = unsafe { &mut *(vm_ptr as *mut RegoVM) };
|
||||
let input_json: String = env.get_string(&input_json)?.into();
|
||||
let input = Value::from_json_str(&input_json)?;
|
||||
vm.set_input(input);
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeSetExecutionMode(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
vm_ptr: jlong,
|
||||
mode: u8,
|
||||
) {
|
||||
let _ = throw_err(env, |_env| {
|
||||
let vm = unsafe { &mut *(vm_ptr as *mut RegoVM) };
|
||||
let mode = match mode {
|
||||
0 => ExecutionMode::RunToCompletion,
|
||||
1 => ExecutionMode::Suspendable,
|
||||
_ => return Err(anyhow::anyhow!("invalid execution mode")),
|
||||
};
|
||||
vm.set_execution_mode(mode);
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeExecute(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
vm_ptr: jlong,
|
||||
) -> jstring {
|
||||
let res = throw_err(env, |env| {
|
||||
let vm = unsafe { &mut *(vm_ptr as *mut RegoVM) };
|
||||
let result = vm.execute()?;
|
||||
let output = env.new_string(result.to_json_str()?)?;
|
||||
Ok(output.into_raw())
|
||||
});
|
||||
|
||||
match res {
|
||||
Ok(val) => val,
|
||||
Err(_) => JObject::null().into_raw(),
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeExecuteEntryPoint(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
vm_ptr: jlong,
|
||||
entry_point: JString,
|
||||
) -> jstring {
|
||||
let res = throw_err(env, |env| {
|
||||
let vm = unsafe { &mut *(vm_ptr as *mut RegoVM) };
|
||||
let entry_point: String = env.get_string(&entry_point)?.into();
|
||||
let result = vm.execute_entry_point_by_name(&entry_point)?;
|
||||
let output = env.new_string(result.to_json_str()?)?;
|
||||
Ok(output.into_raw())
|
||||
});
|
||||
|
||||
match res {
|
||||
Ok(val) => val,
|
||||
Err(_) => JObject::null().into_raw(),
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeResume(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
vm_ptr: jlong,
|
||||
resume_json: JString,
|
||||
has_value: bool,
|
||||
) -> jstring {
|
||||
let res = throw_err(env, |env| {
|
||||
let vm = unsafe { &mut *(vm_ptr as *mut RegoVM) };
|
||||
let value = if has_value {
|
||||
let resume_json: String = env.get_string(&resume_json)?.into();
|
||||
Some(Value::from_json_str(&resume_json)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let result = vm.resume(value)?;
|
||||
let output = env.new_string(result.to_json_str()?)?;
|
||||
Ok(output.into_raw())
|
||||
});
|
||||
|
||||
match res {
|
||||
Ok(val) => val,
|
||||
Err(_) => JObject::null().into_raw(),
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeGetExecutionState(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
vm_ptr: jlong,
|
||||
) -> jstring {
|
||||
let res = throw_err(env, |env| {
|
||||
let vm = unsafe { &mut *(vm_ptr as *mut RegoVM) };
|
||||
let output = env.new_string(format!("{:?}", vm.execution_state()))?;
|
||||
Ok(output.into_raw())
|
||||
});
|
||||
|
||||
match res {
|
||||
Ok(val) => val,
|
||||
Err(_) => JObject::null().into_raw(),
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeDrop(
|
||||
_env: JNIEnv,
|
||||
_class: JClass,
|
||||
vm_ptr: jlong,
|
||||
) {
|
||||
unsafe {
|
||||
let _vm = Box::from_raw(vm_ptr as *mut RegoVM);
|
||||
}
|
||||
}
|
||||
|
||||
fn throw_err<T>(mut env: JNIEnv, mut f: impl FnMut(&mut JNIEnv) -> Result<T>) -> Result<T> {
|
||||
match f(&mut env) {
|
||||
Ok(val) => Ok(val),
|
||||
@@ -379,3 +715,19 @@ fn throw_err<T>(mut env: JNIEnv, mut f: impl FnMut(&mut JNIEnv) -> Result<T>) ->
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_string_array(env: &mut JNIEnv, array: jobjectArray) -> Result<Vec<String>> {
|
||||
if array.is_null() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
let array = unsafe { JObjectArray::from_raw(array) };
|
||||
let len = env.get_array_length(&array)?;
|
||||
let mut values = Vec::with_capacity(len as usize);
|
||||
for i in 0..len {
|
||||
let obj = env.get_object_array_element(&array, i)?;
|
||||
let jstr = JString::from(obj);
|
||||
let value: String = env.get_string(&jstr)?.into();
|
||||
values.push(value);
|
||||
}
|
||||
Ok(values)
|
||||
}
|
||||
|
||||
@@ -221,6 +221,8 @@ public class Engine implements AutoCloseable, Cloneable {
|
||||
|
||||
/**
|
||||
* Get coverage report as json string.
|
||||
*
|
||||
* @return Coverage report as a JSON string.
|
||||
*
|
||||
*/
|
||||
public String getCoverageReport() {
|
||||
@@ -229,6 +231,8 @@ public class Engine implements AutoCloseable, Cloneable {
|
||||
|
||||
/**
|
||||
* Get coverage report as ANSI color coded string.
|
||||
*
|
||||
* @return Coverage report formatted for console output.
|
||||
*
|
||||
*/
|
||||
public String getCoverageReportPretty() {
|
||||
@@ -247,12 +251,18 @@ public class Engine implements AutoCloseable, Cloneable {
|
||||
|
||||
/**
|
||||
* Take gathered prints.
|
||||
*
|
||||
* @return Collected print output as JSON.
|
||||
*
|
||||
*/
|
||||
public String takePrints() {
|
||||
return nativeTakePrints(enginePtr);
|
||||
}
|
||||
|
||||
long getPtr() {
|
||||
return enginePtr;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
* Licensed under the MIT License.
|
||||
**/
|
||||
|
||||
package com.microsoft.regorus;
|
||||
|
||||
/**
|
||||
* Represents a Rego module used for RVM program compilation.
|
||||
*/
|
||||
public final class PolicyModule {
|
||||
/**
|
||||
* Module identifier or filename.
|
||||
*/
|
||||
public final String id;
|
||||
|
||||
/**
|
||||
* Rego policy content.
|
||||
*/
|
||||
public final String content;
|
||||
|
||||
/**
|
||||
* Create a new policy module.
|
||||
*
|
||||
* @param id Module identifier or filename.
|
||||
* @param content Rego policy content.
|
||||
*/
|
||||
public PolicyModule(String id, String content) {
|
||||
this.id = id;
|
||||
this.content = content;
|
||||
}
|
||||
}
|
||||
102
bindings/java/src/main/java/com/microsoft/regorus/Program.java
Normal file
102
bindings/java/src/main/java/com/microsoft/regorus/Program.java
Normal file
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
* Licensed under the MIT License.
|
||||
**/
|
||||
|
||||
package com.microsoft.regorus;
|
||||
|
||||
/**
|
||||
* Represents a compiled RVM program.
|
||||
*/
|
||||
public final class Program implements AutoCloseable {
|
||||
private static native long nativeCompileFromModules(
|
||||
String dataJson,
|
||||
String[] moduleIds,
|
||||
String[] moduleContents,
|
||||
String[] entryPoints);
|
||||
|
||||
private static native long nativeCompileFromEngine(long enginePtr, String[] entryPoints);
|
||||
private static native String nativeGenerateListing(long programPtr);
|
||||
private static native byte[] nativeSerializeBinary(long programPtr);
|
||||
private static native long nativeDeserializeBinary(byte[] data, boolean[] isPartial);
|
||||
private static native void nativeDrop(long programPtr);
|
||||
|
||||
private final long programPtr;
|
||||
|
||||
Program(long ptr) {
|
||||
this.programPtr = ptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile a program from modules and entry points.
|
||||
*
|
||||
* @param dataJson JSON document to merge as static data.
|
||||
* @param modules Policy modules to compile.
|
||||
* @param entryPoints Entry point rule paths.
|
||||
* @return Compiled program instance.
|
||||
*/
|
||||
public static Program compileFromModules(String dataJson, PolicyModule[] modules, String[] entryPoints) {
|
||||
String[] ids = new String[modules.length];
|
||||
String[] contents = new String[modules.length];
|
||||
for (int i = 0; i < modules.length; i++) {
|
||||
ids[i] = modules[i].id;
|
||||
contents[i] = modules[i].content;
|
||||
}
|
||||
long ptr = nativeCompileFromModules(dataJson, ids, contents, entryPoints);
|
||||
return new Program(ptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile a program from an engine and entry points.
|
||||
*
|
||||
* @param engine Engine with loaded policies.
|
||||
* @param entryPoints Entry point rule paths.
|
||||
* @return Compiled program instance.
|
||||
*/
|
||||
public static Program compileFromEngine(Engine engine, String[] entryPoints) {
|
||||
long ptr = nativeCompileFromEngine(engine.getPtr(), entryPoints);
|
||||
return new Program(ptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a readable assembly listing.
|
||||
*
|
||||
* @return Listing text.
|
||||
*/
|
||||
public String generateListing() {
|
||||
return nativeGenerateListing(programPtr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the program to binary format.
|
||||
*
|
||||
* @return Serialized bytes.
|
||||
*/
|
||||
public byte[] serializeBinary() {
|
||||
return nativeSerializeBinary(programPtr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a program from binary format.
|
||||
*
|
||||
* @param data Serialized program bytes.
|
||||
* @param isPartial Optional array to receive the partial flag (index 0).
|
||||
* @return Deserialized program instance.
|
||||
*/
|
||||
public static Program deserializeBinary(byte[] data, boolean[] isPartial) {
|
||||
if (data == null || data.length == 0) {
|
||||
throw new IllegalArgumentException("data must not be empty");
|
||||
}
|
||||
long ptr = nativeDeserializeBinary(data, isPartial);
|
||||
return new Program(ptr);
|
||||
}
|
||||
|
||||
long getPtr() {
|
||||
return programPtr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
nativeDrop(programPtr);
|
||||
}
|
||||
}
|
||||
110
bindings/java/src/main/java/com/microsoft/regorus/Rvm.java
Normal file
110
bindings/java/src/main/java/com/microsoft/regorus/Rvm.java
Normal file
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
* Licensed under the MIT License.
|
||||
**/
|
||||
|
||||
package com.microsoft.regorus;
|
||||
|
||||
/**
|
||||
* Wrapper for the Regorus RVM runtime.
|
||||
*/
|
||||
public final class Rvm implements AutoCloseable {
|
||||
private static native long nativeNew();
|
||||
private static native void nativeDrop(long vmPtr);
|
||||
private static native void nativeLoadProgram(long vmPtr, long programPtr);
|
||||
private static native void nativeSetDataJson(long vmPtr, String dataJson);
|
||||
private static native void nativeSetInputJson(long vmPtr, String inputJson);
|
||||
private static native void nativeSetExecutionMode(long vmPtr, byte mode);
|
||||
private static native String nativeExecute(long vmPtr);
|
||||
private static native String nativeExecuteEntryPoint(long vmPtr, String entryPoint);
|
||||
private static native String nativeResume(long vmPtr, String resumeJson, boolean hasValue);
|
||||
private static native String nativeGetExecutionState(long vmPtr);
|
||||
|
||||
private final long vmPtr;
|
||||
|
||||
/**
|
||||
* Create a new RVM instance.
|
||||
*/
|
||||
public Rvm() {
|
||||
this.vmPtr = nativeNew();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a program into the VM.
|
||||
*
|
||||
* @param program Compiled program.
|
||||
*/
|
||||
public void loadProgram(Program program) {
|
||||
nativeLoadProgram(vmPtr, program.getPtr());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set data JSON for the VM.
|
||||
*
|
||||
* @param dataJson JSON data document.
|
||||
*/
|
||||
public void setDataJson(String dataJson) {
|
||||
nativeSetDataJson(vmPtr, dataJson);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set input JSON for the VM.
|
||||
*
|
||||
* @param inputJson JSON input document.
|
||||
*/
|
||||
public void setInputJson(String inputJson) {
|
||||
nativeSetInputJson(vmPtr, inputJson);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set execution mode (0 = run-to-completion, 1 = suspendable).
|
||||
*
|
||||
* @param mode Execution mode.
|
||||
*/
|
||||
public void setExecutionMode(byte mode) {
|
||||
nativeSetExecutionMode(vmPtr, mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the program.
|
||||
*
|
||||
* @return JSON result string.
|
||||
*/
|
||||
public String execute() {
|
||||
return nativeExecute(vmPtr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a named entry point.
|
||||
*
|
||||
* @param entryPoint Entry point rule path.
|
||||
* @return JSON result string.
|
||||
*/
|
||||
public String executeEntryPoint(String entryPoint) {
|
||||
return nativeExecuteEntryPoint(vmPtr, entryPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume execution with an optional JSON value.
|
||||
*
|
||||
* @param resumeJson JSON value to resume with, or null for no value.
|
||||
* @return JSON result string.
|
||||
*/
|
||||
public String resume(String resumeJson) {
|
||||
return nativeResume(vmPtr, resumeJson, resumeJson != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current execution state.
|
||||
*
|
||||
* @return Execution state string.
|
||||
*/
|
||||
public String getExecutionState() {
|
||||
return nativeGetExecutionState(vmPtr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
nativeDrop(vmPtr);
|
||||
}
|
||||
}
|
||||
40
bindings/python/Cargo.lock
generated
40
bindings/python/Cargo.lock
generated
@@ -52,6 +52,16 @@ version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"unty",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.8.0"
|
||||
@@ -103,9 +113,9 @@ checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.54"
|
||||
version = "1.2.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583"
|
||||
checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
@@ -191,9 +201,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.8"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
|
||||
[[package]]
|
||||
name = "fluent-uri"
|
||||
@@ -263,9 +273,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.64"
|
||||
version = "0.1.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
|
||||
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
@@ -395,6 +405,8 @@ checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -876,10 +888,12 @@ name = "regorus"
|
||||
version = "0.9.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
"chrono",
|
||||
"chrono-tz",
|
||||
"data-encoding",
|
||||
"globset",
|
||||
"indexmap",
|
||||
"ipnet",
|
||||
"jsonschema",
|
||||
"lazy_static",
|
||||
@@ -1110,6 +1124,12 @@ version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
||||
|
||||
[[package]]
|
||||
name = "unty"
|
||||
version = "0.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.8"
|
||||
@@ -1313,18 +1333,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.34"
|
||||
version = "0.8.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71ddd76bcebeed25db614f82bf31a9f4222d3fbba300e6fb6c00afa26cbd4d9d"
|
||||
checksum = "dafd85c832c1b68bbb4ec0c72c7f6f4fc5179627d2bc7c26b30e4c0cc11e76cc"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.34"
|
||||
version = "0.8.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8187381b52e32220d50b255276aa16a084ec0a9017a0ca2152a1f55c539758d"
|
||||
checksum = "7cb7e4e8436d9db52fbd6625dbf2f45243ab84994a72882ec8227b99e72b439a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -22,6 +22,6 @@ coverage = ["regorus/coverage"]
|
||||
anyhow = "1.0"
|
||||
ordered-float = "5.0.0"
|
||||
pyo3 = { version = "0.24.1", features = ["abi3-py310", "anyhow", "extension-module"] }
|
||||
regorus = { path = "../..", default-features = false, features = ["arc"] }
|
||||
regorus = { path = "../..", default-features = false, features = ["arc", "rvm"] }
|
||||
serde_json = "1.0.140"
|
||||
|
||||
|
||||
@@ -8,7 +8,14 @@ use pyo3::IntoPyObjectExt;
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use ::regorus::Value;
|
||||
use ::regorus::languages::rego::compiler::Compiler;
|
||||
use ::regorus::rvm::program::{
|
||||
generate_assembly_listing, generate_tabular_assembly_listing, AssemblyListingConfig,
|
||||
DeserializationResult, Program as RvmProgram,
|
||||
};
|
||||
use ::regorus::rvm::vm::{ExecutionMode, RegoVM};
|
||||
use ::regorus::{compile_policy_with_entrypoint, PolicyModule, Rc, Value};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Regorus engine.
|
||||
#[pyclass(unsendable)]
|
||||
@@ -16,6 +23,18 @@ pub struct Engine {
|
||||
engine: ::regorus::Engine,
|
||||
}
|
||||
|
||||
/// RVM program wrapper.
|
||||
#[pyclass(unsendable)]
|
||||
pub struct Program {
|
||||
program: Arc<RvmProgram>,
|
||||
}
|
||||
|
||||
/// RVM runtime wrapper.
|
||||
#[pyclass(unsendable)]
|
||||
pub struct Rvm {
|
||||
vm: RegoVM,
|
||||
}
|
||||
|
||||
impl Default for Engine {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
@@ -384,7 +403,151 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl Program {
|
||||
/// Compile an RVM program from modules and entry points.
|
||||
#[staticmethod]
|
||||
pub fn compile_from_modules(
|
||||
data_json: String,
|
||||
modules: Vec<(String, String)>,
|
||||
entry_points: Vec<String>,
|
||||
) -> Result<Self> {
|
||||
if entry_points.is_empty() {
|
||||
return Err(anyhow!("entry_points must contain at least one entry"));
|
||||
}
|
||||
|
||||
let data = Value::from_json_str(&data_json)?;
|
||||
let policy_modules: Vec<PolicyModule> = modules
|
||||
.into_iter()
|
||||
.map(|(id, content)| PolicyModule {
|
||||
id: Rc::from(id.as_str()),
|
||||
content: Rc::from(content.as_str()),
|
||||
})
|
||||
.collect();
|
||||
|
||||
let entry_points_ref: Vec<&str> = entry_points.iter().map(|s| s.as_str()).collect();
|
||||
let entry_rule = Rc::from(entry_points_ref[0]);
|
||||
let compiled = compile_policy_with_entrypoint(data, &policy_modules, entry_rule)?;
|
||||
let program = Compiler::compile_from_policy(&compiled, &entry_points_ref)?;
|
||||
Ok(Self { program })
|
||||
}
|
||||
|
||||
/// Deserialize an RVM program from binary data.
|
||||
#[staticmethod]
|
||||
pub fn deserialize_binary(data: Vec<u8>) -> Result<(Self, bool)> {
|
||||
let (program, is_partial) =
|
||||
match RvmProgram::deserialize_binary(&data).map_err(|e: String| anyhow!(e))? {
|
||||
DeserializationResult::Complete(program) => (program, false),
|
||||
DeserializationResult::Partial(program) => (program, true),
|
||||
};
|
||||
Ok((
|
||||
Self {
|
||||
program: Arc::new(program),
|
||||
},
|
||||
is_partial,
|
||||
))
|
||||
}
|
||||
|
||||
/// Serialize a program to binary format.
|
||||
pub fn serialize_binary(&self) -> Result<Vec<u8>> {
|
||||
self.program
|
||||
.serialize_binary()
|
||||
.map_err(|e: String| anyhow!(e))
|
||||
}
|
||||
|
||||
/// Generate a readable assembly listing.
|
||||
pub fn generate_listing(&self) -> Result<String> {
|
||||
Ok(generate_assembly_listing(
|
||||
self.program.as_ref(),
|
||||
&AssemblyListingConfig::default(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Generate a tabular assembly listing.
|
||||
pub fn generate_tabular_listing(&self) -> Result<String> {
|
||||
Ok(generate_tabular_assembly_listing(
|
||||
self.program.as_ref(),
|
||||
&AssemblyListingConfig::default(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Rvm {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl Rvm {
|
||||
#[new]
|
||||
pub fn new() -> Self {
|
||||
Self { vm: RegoVM::new() }
|
||||
}
|
||||
|
||||
/// Load an RVM program into the VM.
|
||||
pub fn load_program(&mut self, program: &Program) -> Result<()> {
|
||||
self.vm.load_program(program.program.clone());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set data JSON for the VM.
|
||||
pub fn set_data_json(&mut self, data_json: String) -> Result<()> {
|
||||
let data = Value::from_json_str(&data_json)?;
|
||||
self.vm.set_data(data)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set input JSON for the VM.
|
||||
pub fn set_input_json(&mut self, input_json: String) -> Result<()> {
|
||||
let input = Value::from_json_str(&input_json)?;
|
||||
self.vm.set_input(input);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set execution mode (0 = run-to-completion, 1 = suspendable).
|
||||
pub fn set_execution_mode(&mut self, mode: u8) -> Result<()> {
|
||||
let mode = match mode {
|
||||
0 => ExecutionMode::RunToCompletion,
|
||||
1 => ExecutionMode::Suspendable,
|
||||
_ => return Err(anyhow!("invalid execution mode")),
|
||||
};
|
||||
self.vm.set_execution_mode(mode);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Execute the program and return the JSON result.
|
||||
pub fn execute(&mut self) -> Result<String> {
|
||||
self.vm.execute()?.to_json_str()
|
||||
}
|
||||
|
||||
/// Execute an entry point by name and return the JSON result.
|
||||
pub fn execute_entry_point(&mut self, entry_point: String) -> Result<String> {
|
||||
self.vm
|
||||
.execute_entry_point_by_name(&entry_point)?
|
||||
.to_json_str()
|
||||
}
|
||||
|
||||
/// Resume execution with an optional JSON value.
|
||||
pub fn resume(&mut self, resume_json: Option<String>) -> Result<String> {
|
||||
let value = if let Some(json) = resume_json {
|
||||
Some(Value::from_json_str(&json)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.vm.resume(value)?.to_json_str()
|
||||
}
|
||||
|
||||
/// Get the execution state as a string.
|
||||
pub fn get_execution_state(&self) -> Result<String> {
|
||||
Ok(format!("{:?}", self.vm.execution_state()))
|
||||
}
|
||||
}
|
||||
|
||||
#[pymodule]
|
||||
pub fn regorus(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_class::<crate::Engine>()
|
||||
m.add_class::<crate::Engine>()?;
|
||||
m.add_class::<crate::Program>()?;
|
||||
m.add_class::<crate::Rvm>()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
# Licensed under the MIT License.
|
||||
|
||||
import regorus
|
||||
import sys
|
||||
|
||||
if hasattr(sys.stdout, "reconfigure"):
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
|
||||
# Create engine
|
||||
engine = regorus.Engine()
|
||||
@@ -94,3 +98,68 @@ engine1.set_gather_prints(True)
|
||||
engine1.eval_query('print("Hello")')
|
||||
ps = engine1.take_prints()
|
||||
print(ps)
|
||||
|
||||
# RVM regular example
|
||||
policy = """
|
||||
package demo
|
||||
import rego.v1
|
||||
|
||||
default allow := false
|
||||
|
||||
allow if {
|
||||
input.user == "alice"
|
||||
input.active == true
|
||||
}
|
||||
"""
|
||||
def run_regular_example():
|
||||
module = ("demo.rego", policy)
|
||||
program = regorus.Program.compile_from_modules(
|
||||
"{}",
|
||||
[module],
|
||||
["data.demo.allow"],
|
||||
)
|
||||
|
||||
print(program.generate_listing())
|
||||
|
||||
binary = program.serialize_binary()
|
||||
program, is_partial = regorus.Program.deserialize_binary(binary)
|
||||
if is_partial:
|
||||
raise RuntimeError("Deserialized program marked partial")
|
||||
|
||||
vm = regorus.Rvm()
|
||||
vm.load_program(program)
|
||||
vm.set_input_json('{"user":"alice","active":true}')
|
||||
print(vm.execute())
|
||||
|
||||
run_regular_example()
|
||||
|
||||
# RVM HostAwait example
|
||||
policy = """
|
||||
package demo
|
||||
import rego.v1
|
||||
|
||||
default allow := false
|
||||
|
||||
allow if {
|
||||
input.account.active == true
|
||||
details := __builtin_host_await(input.account.id, "account")
|
||||
details.tier == "gold"
|
||||
}
|
||||
"""
|
||||
def run_host_await_example():
|
||||
module = ("await.rego", policy)
|
||||
program = regorus.Program.compile_from_modules(
|
||||
"{}",
|
||||
[module],
|
||||
["data.demo.allow"],
|
||||
)
|
||||
|
||||
vm = regorus.Rvm()
|
||||
vm.set_execution_mode(1)
|
||||
vm.load_program(program)
|
||||
vm.set_input_json('{"account":{"id":"acct-1","active":true}}')
|
||||
vm.execute()
|
||||
print(vm.get_execution_state())
|
||||
print(vm.resume('{"tier":"gold"}'))
|
||||
|
||||
run_host_await_example()
|
||||
|
||||
41
bindings/wasm/Cargo.lock
generated
41
bindings/wasm/Cargo.lock
generated
@@ -63,6 +63,16 @@ version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"unty",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.8.0"
|
||||
@@ -120,9 +130,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.54"
|
||||
version = "1.2.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583"
|
||||
checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
@@ -208,9 +218,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.8"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
|
||||
[[package]]
|
||||
name = "fluent-uri"
|
||||
@@ -314,9 +324,9 @@ checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.64"
|
||||
version = "0.1.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
|
||||
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
@@ -446,6 +456,8 @@ checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -874,10 +886,12 @@ name = "regorus"
|
||||
version = "0.9.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
"chrono",
|
||||
"chrono-tz",
|
||||
"data-encoding",
|
||||
"globset",
|
||||
"indexmap",
|
||||
"ipnet",
|
||||
"jsonschema",
|
||||
"lazy_static",
|
||||
@@ -903,6 +917,7 @@ dependencies = [
|
||||
"getrandom 0.2.17",
|
||||
"getrandom 0.3.4",
|
||||
"regorus",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"uuid",
|
||||
"wasm-bindgen",
|
||||
@@ -1098,6 +1113,12 @@ version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
||||
|
||||
[[package]]
|
||||
name = "unty"
|
||||
version = "0.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.8"
|
||||
@@ -1398,18 +1419,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.34"
|
||||
version = "0.8.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71ddd76bcebeed25db614f82bf31a9f4222d3fbba300e6fb6c00afa26cbd4d9d"
|
||||
checksum = "dafd85c832c1b68bbb4ec0c72c7f6f4fc5179627d2bc7c26b30e4c0cc11e76cc"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.34"
|
||||
version = "0.8.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8187381b52e32220d50b255276aa16a084ec0a9017a0ca2152a1f55c539758d"
|
||||
checksum = "7cb7e4e8436d9db52fbd6625dbf2f45243ab84994a72882ec8227b99e72b439a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -37,7 +37,8 @@ ast = ["regorus/ast"]
|
||||
coverage = ["regorus/coverage"]
|
||||
|
||||
[dependencies]
|
||||
regorus = { path = "../..", default-features = false, features = ["arc"] }
|
||||
regorus = { path = "../..", default-features = false, features = ["arc", "rvm"] }
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = "1.0.140"
|
||||
wasm-bindgen = "0.2.100"
|
||||
# Specify uuid as a mandatory dependency so as to enable `js` feature which is now required
|
||||
|
||||
@@ -3,6 +3,15 @@
|
||||
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use regorus::languages::rego::compiler::Compiler;
|
||||
use regorus::rvm::program::{
|
||||
generate_assembly_listing, generate_tabular_assembly_listing, AssemblyListingConfig,
|
||||
DeserializationResult, Program as RvmProgram,
|
||||
};
|
||||
use regorus::rvm::vm::{ExecutionMode, RegoVM};
|
||||
use regorus::{compile_policy_with_entrypoint, PolicyModule, Rc, Value};
|
||||
use serde::Deserialize;
|
||||
use std::sync::Arc;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
@@ -11,6 +20,44 @@ pub struct Engine {
|
||||
engine: regorus::Engine,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ModuleSpec {
|
||||
id: String,
|
||||
content: String,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct Program {
|
||||
program: Arc<RvmProgram>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct ProgramDeserializationResult {
|
||||
program: Arc<RvmProgram>,
|
||||
is_partial: bool,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl ProgramDeserializationResult {
|
||||
/// Whether the program was partially deserialized.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn isPartial(&self) -> bool {
|
||||
self.is_partial
|
||||
}
|
||||
|
||||
/// Get the deserialized program.
|
||||
pub fn program(&self) -> Program {
|
||||
Program {
|
||||
program: self.program.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct Rvm {
|
||||
vm: RegoVM,
|
||||
}
|
||||
|
||||
fn error_to_jsvalue<E: std::fmt::Display>(e: E) -> JsValue {
|
||||
JsValue::from_str(&format!("{e}"))
|
||||
}
|
||||
@@ -193,6 +240,153 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Program {
|
||||
/// Compile an RVM program from modules and entry points.
|
||||
pub fn compileFromModules(
|
||||
data_json: String,
|
||||
modules_json: String,
|
||||
entry_points_json: String,
|
||||
) -> Result<Program, JsValue> {
|
||||
let data = Value::from_json_str(&data_json).map_err(error_to_jsvalue)?;
|
||||
let modules: Vec<ModuleSpec> =
|
||||
serde_json::from_str(&modules_json).map_err(error_to_jsvalue)?;
|
||||
let entry_points: Vec<String> =
|
||||
serde_json::from_str(&entry_points_json).map_err(error_to_jsvalue)?;
|
||||
if entry_points.is_empty() {
|
||||
return Err(error_to_jsvalue(
|
||||
"entry_points must contain at least one entry",
|
||||
));
|
||||
}
|
||||
|
||||
let policy_modules: Vec<PolicyModule> = modules
|
||||
.into_iter()
|
||||
.map(|module| PolicyModule {
|
||||
id: Rc::from(module.id.as_str()),
|
||||
content: Rc::from(module.content.as_str()),
|
||||
})
|
||||
.collect();
|
||||
|
||||
let entry_points_ref: Vec<&str> = entry_points.iter().map(|s| s.as_str()).collect();
|
||||
let compiled =
|
||||
compile_policy_with_entrypoint(data, &policy_modules, Rc::from(entry_points_ref[0]))
|
||||
.map_err(error_to_jsvalue)?;
|
||||
let program = Compiler::compile_from_policy(&compiled, &entry_points_ref)
|
||||
.map_err(error_to_jsvalue)?;
|
||||
Ok(Program { program })
|
||||
}
|
||||
|
||||
/// Serialize a program to binary format.
|
||||
pub fn serializeBinary(&self) -> Result<Vec<u8>, JsValue> {
|
||||
self.program
|
||||
.serialize_binary()
|
||||
.map_err(|e| error_to_jsvalue(e.to_string()))
|
||||
}
|
||||
|
||||
/// Deserialize an RVM program from binary format.
|
||||
pub fn deserializeBinary(data: Vec<u8>) -> Result<ProgramDeserializationResult, JsValue> {
|
||||
let (program, is_partial) =
|
||||
match RvmProgram::deserialize_binary(&data).map_err(error_to_jsvalue)? {
|
||||
DeserializationResult::Complete(program) => (program, false),
|
||||
DeserializationResult::Partial(program) => (program, true),
|
||||
};
|
||||
Ok(ProgramDeserializationResult {
|
||||
program: Arc::new(program),
|
||||
is_partial,
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate a readable assembly listing.
|
||||
pub fn generateListing(&self) -> Result<String, JsValue> {
|
||||
Ok(generate_assembly_listing(
|
||||
self.program.as_ref(),
|
||||
&AssemblyListingConfig::default(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Generate a tabular assembly listing.
|
||||
pub fn generateTabularListing(&self) -> Result<String, JsValue> {
|
||||
Ok(generate_tabular_assembly_listing(
|
||||
self.program.as_ref(),
|
||||
&AssemblyListingConfig::default(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Rvm {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new() -> Self {
|
||||
Self { vm: RegoVM::new() }
|
||||
}
|
||||
|
||||
/// Load a program into the VM.
|
||||
pub fn loadProgram(&mut self, program: &Program) {
|
||||
self.vm.load_program(program.program.clone());
|
||||
}
|
||||
|
||||
/// Set VM data from JSON.
|
||||
pub fn setDataJson(&mut self, data_json: String) -> Result<(), JsValue> {
|
||||
let data = Value::from_json_str(&data_json).map_err(error_to_jsvalue)?;
|
||||
self.vm.set_data(data).map_err(error_to_jsvalue)
|
||||
}
|
||||
|
||||
/// Set VM input from JSON.
|
||||
pub fn setInputJson(&mut self, input_json: String) -> Result<(), JsValue> {
|
||||
let input = Value::from_json_str(&input_json).map_err(error_to_jsvalue)?;
|
||||
self.vm.set_input(input);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set execution mode (0 = run-to-completion, 1 = suspendable).
|
||||
pub fn setExecutionMode(&mut self, mode: u8) -> Result<(), JsValue> {
|
||||
let mode = match mode {
|
||||
0 => ExecutionMode::RunToCompletion,
|
||||
1 => ExecutionMode::Suspendable,
|
||||
_ => return Err(error_to_jsvalue("invalid execution mode")),
|
||||
};
|
||||
self.vm.set_execution_mode(mode);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Execute the program and return the JSON result.
|
||||
pub fn execute(&mut self) -> Result<String, JsValue> {
|
||||
let value = self.vm.execute().map_err(error_to_jsvalue)?;
|
||||
value.to_json_str().map_err(error_to_jsvalue)
|
||||
}
|
||||
|
||||
/// Execute an entry point by name and return the JSON result.
|
||||
pub fn executeEntryPoint(&mut self, entry_point: String) -> Result<String, JsValue> {
|
||||
let value = self
|
||||
.vm
|
||||
.execute_entry_point_by_name(&entry_point)
|
||||
.map_err(error_to_jsvalue)?;
|
||||
value.to_json_str().map_err(error_to_jsvalue)
|
||||
}
|
||||
|
||||
/// Resume execution with an optional JSON value.
|
||||
pub fn resume(&mut self, resume_json: Option<String>) -> Result<String, JsValue> {
|
||||
let value = if let Some(json) = resume_json {
|
||||
Some(Value::from_json_str(&json).map_err(error_to_jsvalue)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let result = self.vm.resume(value).map_err(error_to_jsvalue)?;
|
||||
result.to_json_str().map_err(error_to_jsvalue)
|
||||
}
|
||||
|
||||
/// Get the execution state as a string.
|
||||
pub fn getExecutionState(&self) -> String {
|
||||
format!("{:?}", self.vm.execution_state())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Rvm {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::error_to_jsvalue;
|
||||
|
||||
@@ -85,3 +85,78 @@ console.log(report);
|
||||
// Print pretty report.
|
||||
report = engine.getCoverageReportPretty();
|
||||
console.log(report);
|
||||
|
||||
// RVM regular example
|
||||
{
|
||||
const policy = `
|
||||
package demo
|
||||
import rego.v1
|
||||
|
||||
default allow := false
|
||||
|
||||
allow if {
|
||||
input.user == "alice"
|
||||
input.active == true
|
||||
}
|
||||
`;
|
||||
|
||||
const modules = JSON.stringify([
|
||||
{ id: "demo.rego", content: policy }
|
||||
]);
|
||||
const entryPoints = JSON.stringify(["data.demo.allow"]);
|
||||
|
||||
const program = regorus.Program.compileFromModules(
|
||||
"{}",
|
||||
modules,
|
||||
entryPoints
|
||||
);
|
||||
|
||||
console.log(program.generateListing());
|
||||
|
||||
const binary = program.serializeBinary();
|
||||
const deserialized = regorus.Program.deserializeBinary(binary);
|
||||
if (deserialized.isPartial) {
|
||||
throw new Error("Deserialized program marked partial");
|
||||
}
|
||||
const rehydrated = deserialized.program();
|
||||
|
||||
const vm = new regorus.Rvm();
|
||||
vm.loadProgram(rehydrated);
|
||||
vm.setInputJson('{"user":"alice","active":true}');
|
||||
console.log(vm.execute());
|
||||
}
|
||||
|
||||
// RVM HostAwait example
|
||||
{
|
||||
const policy = `
|
||||
package demo
|
||||
import rego.v1
|
||||
|
||||
default allow := false
|
||||
|
||||
allow if {
|
||||
input.account.active == true
|
||||
details := __builtin_host_await(input.account.id, "account")
|
||||
details.tier == "gold"
|
||||
}
|
||||
`;
|
||||
|
||||
const modules = JSON.stringify([
|
||||
{ id: "await.rego", content: policy }
|
||||
]);
|
||||
const entryPoints = JSON.stringify(["data.demo.allow"]);
|
||||
|
||||
const program = regorus.Program.compileFromModules(
|
||||
"{}",
|
||||
modules,
|
||||
entryPoints
|
||||
);
|
||||
|
||||
const vm = new regorus.Rvm();
|
||||
vm.setExecutionMode(1);
|
||||
vm.loadProgram(program);
|
||||
vm.setInputJson('{"account":{"id":"acct-1","active":true}}');
|
||||
vm.execute();
|
||||
console.log(vm.getExecutionState());
|
||||
console.log(vm.resume('{"tier":"gold"}'));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user