Extend prepare API/docs across bindings

Agent-Logs-Url: https://github.com/microsoft/regorus/sessions/49ba4462-95d3-42c0-a302-db1b81df4f65

Co-authored-by: anakrish <35780660+anakrish@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-05-14 13:13:43 +00:00
committed by GitHub
parent 72515f6d4c
commit 730e6de75a
16 changed files with 129 additions and 6 deletions

View File

@@ -80,6 +80,10 @@ namespace regorus {
return std::unique_ptr<Engine>(new Engine(regorus_engine_clone(engine)));
}
Result prepare() {
return Result(regorus_engine_prepare(engine));
}
Result set_rego_v0(bool enable) {
return Result(regorus_engine_set_rego_v0(engine, enable));
}

View File

@@ -68,6 +68,18 @@ namespace Regorus
});
}
/// <summary>
/// Prepare internal evaluation structures without executing a query.
/// This is optional: if skipped, the first evaluation pays this setup cost.
/// </summary>
public void Prepare()
{
UseHandle(enginePtr =>
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_prepare((Regorus.Internal.RegorusEngine*)enginePtr));
});
}
public void SetStrictBuiltinErrors(bool strict)
{
UseHandle(enginePtr =>

View File

@@ -92,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>
/// Prepare a RegorusEngine for evaluation without executing a query.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_prepare", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_prepare(RegorusEngine* engine);
/// <summary>
/// Compile an RVM program from the engine state with entry points.
/// </summary>

View File

@@ -199,6 +199,21 @@ pub extern "C" fn regorus_engine_clone(engine: *mut RegorusEngine) -> *mut Regor
}
}
/// Prepare a [`RegorusEngine`] for evaluation without executing a query.
///
/// This is optional. If not called, first eval performs the same setup.
/// If policy/data changes after preparation, setup is invalidated.
#[no_mangle]
pub extern "C" fn regorus_engine_prepare(engine: *mut RegorusEngine) -> RegorusResult {
with_unwind_guard(|| {
to_regorus_result(|| -> Result<()> {
let engine = to_ref(engine)?;
let mut guard = engine.try_write()?;
guard.prepare()
}())
})
}
#[no_mangle]
pub extern "C" fn regorus_engine_drop(engine: *mut RegorusEngine) {
if let Ok(e) = to_ref(engine) {

View File

@@ -28,6 +28,17 @@ func (e *Engine) Clone() *Engine {
return c
}
func (e *Engine) Prepare() error {
result := C.regorus_engine_prepare(e.e)
defer C.regorus_result_drop(result)
if result.status != C.Ok {
return fmt.Errorf("%s", C.GoString(result.error_message))
}
return nil
}
func (e *Engine) SetRegoV0(enable bool) error {
result := C.regorus_engine_set_rego_v0(e.e, C.bool(enable))
defer C.regorus_result_drop(result)

View File

@@ -23,6 +23,14 @@ JNIEXPORT jlong JNICALL Java_com_microsoft_regorus_Engine_nativeNewEngine
JNIEXPORT jlong JNICALL Java_com_microsoft_regorus_Engine_nativeClone
(JNIEnv *, jclass, jlong);
/*
* Class: com_microsoft_regorus_Engine
* Method: nativePrepare
* Signature: (J)V
*/
JNIEXPORT void JNICALL Java_com_microsoft_regorus_Engine_nativePrepare
(JNIEnv *, jclass, jlong);
/*
* Class: com_microsoft_regorus_Engine
* Method: nativeAddPolicy

View File

@@ -36,6 +36,19 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeClone(
Box::into_raw(Box::new(c)) as jlong
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativePrepare(
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
) {
let _ = throw_err(env, |_env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
engine.prepare()?;
Ok(())
});
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetRegoV0(
env: EnvUnowned,

View File

@@ -21,6 +21,7 @@ public class Engine implements AutoCloseable, Cloneable {
// if you update the native API.
private static native long nativeNewEngine();
private static native long nativeClone(long enginePtr);
private static native void nativePrepare(long enginePtr);
private static native void nativeSetRegoV0(long enginePtr, boolean enable);
private static native String nativeAddPolicy(long enginePtr, String path, String rego);
private static native String nativeAddPolicyFromFile(long enginePtr, String path);
@@ -65,6 +66,14 @@ public class Engine implements AutoCloseable, Cloneable {
public Engine clone() {
return new Engine(nativeClone(enginePtr));
}
/**
* Prepares internal evaluation structures without executing a query.
* Optional: if skipped, first evaluation performs the same setup.
*/
public void prepare() {
nativePrepare(enginePtr);
}
/**
* Enable/disable Rego v0.

View File

@@ -22,6 +22,9 @@ public class EngineTest extends TestCase
"package test\nmessage = concat(\", \", [input.message, data.message])"
);
engine.addDataJson("{\"message\":\"World!\"}");
engine.prepare();
Engine template = engine.clone();
template.close();
engine.setInputJson("{\"message\":\"Hello\"}");
resJson = engine.evalQuery("data.test.message");
}

View File

@@ -463,6 +463,13 @@ impl Engine {
self.engine.take_prints()
}
/// Prepare internal evaluation structures without executing a query.
///
/// Optional: if skipped, first evaluation performs the same setup.
pub fn prepare(&mut self) -> Result<()> {
self.engine.prepare()
}
/// Clone a [`Engine`]
///
/// To avoid having to parse same policy again, the engine can be cloned

View File

@@ -87,6 +87,7 @@ report = engine.get_coverage_report_pretty()
print(report)
# Clone engine
engine.prepare()
engine1 = engine.clone()

View File

@@ -115,6 +115,13 @@ impl Engine {
Ok(())
}
fn prepare(&self) -> Result<(), Error> {
self.engine
.borrow_mut()
.prepare()
.map_err(|e| Error::new(runtime_error(), format!("Failed to prepare engine: {e}")))
}
fn get_packages(&self) -> Result<Vec<String>, Error> {
self.engine
.borrow()
@@ -373,6 +380,7 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
method!(Engine::add_data_from_json_file, 1),
)?;
engine_class.define_method("clear_data", method!(Engine::clear_data, 0))?;
engine_class.define_method("prepare", method!(Engine::prepare, 0))?;
// input operations
engine_class.define_method("set_input", method!(Engine::set_input, 1))?;

View File

@@ -150,6 +150,7 @@ class TestRegorus < Minitest::Test
end
def test_engine_cloning
@engine.prepare
cloned_engine = @engine.clone
assert_instance_of ::Regorus::Engine, cloned_engine

View File

@@ -23,4 +23,7 @@ Run `cargo xtask build-wasm` to invoke wasm-pack with sensible defaults, or `car
See [test.js](https://github.com/microsoft/regorus/blob/main/bindings/wasm/test.js) for example usage.
For best performance with large policies, call `engine.prepare()` after loading
policy/data, then use `engine.clone()` to create per-request engines.
policy/data, then use `engine.clone()` to create per-request engines. If
`prepare()` is skipped, the first `eval*` call performs the same one-time
setup. Adding/changing policy or data after `prepare()` invalidates the
prepared state.

View File

@@ -141,6 +141,9 @@ impl Engine {
/// Clone this engine.
///
/// Useful for creating per-request engines after loading policy/data once.
///
/// Clone is designed to avoid reparsing policy text and reloading immutable
/// policy structures. Mutable evaluation state is copied for isolation.
#[wasm_bindgen(js_name = "clone")]
pub fn cloneEngine(&self) -> Engine {
Clone::clone(self)
@@ -168,8 +171,14 @@ impl Engine {
/// Prepare the engine for evaluation.
///
/// This initializes internal evaluation structures so a cloned engine can
/// evaluate without requiring an initial "dummy" evaluation.
/// The first evaluation on an unprepared engine performs one-time setup.
/// Calling `prepare()` performs that setup eagerly.
///
/// This is optional for correctness. If omitted, the first `eval*` call
/// implicitly performs preparation.
///
/// If policies/data are modified after `prepare()`, preparation is
/// invalidated and must be performed again (explicitly or via first eval).
pub fn prepare(&mut self) -> Result<(), JsValue> {
self.engine.prepare().map_err(error_to_jsvalue)
}

View File

@@ -507,9 +507,22 @@ impl Engine {
/// Prepare the engine for evaluation without executing a query or rule.
///
/// This parses and initializes internal evaluation data structures so that
/// subsequent evaluations (or cloned engines) can run without paying the
/// one-time preparation cost during the first evaluation call.
/// The first evaluation on an unprepared engine performs one-time setup
/// (analysis, scheduling, imports/rules processing, and initialization of
/// internal evaluation structures). Calling this method performs that work
/// eagerly so a later call to [`Engine::eval_rule`] / [`Engine::eval_query`]
/// does not pay that startup cost.
///
/// This method is optional for correctness. If omitted, the first
/// evaluation will implicitly prepare the engine.
///
/// Preparation is invalidated when policy/data that affects evaluation is
/// changed (for example: [`Engine::add_policy`], [`Engine::add_policy_from_file`],
/// [`Engine::add_data`], [`Engine::clear_data`]). In those cases, the next
/// evaluation (or another explicit call to `prepare`) performs setup again.
///
/// This is especially useful before cloning template engines used for
/// repeated evaluations.
///
/// ```
/// # use regorus::*;