diff --git a/bindings/ffi/src/lib.rs b/bindings/ffi/src/lib.rs index f882f03f..011c5d67 100644 --- a/bindings/ffi/src/lib.rs +++ b/bindings/ffi/src/lib.rs @@ -189,7 +189,6 @@ pub extern "C" fn regorus_engine_add_data_json( /// Get list of loaded Rego packages as JSON. /// /// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_packages -/// * `data`: JSON encoded value to be used as policy data. #[no_mangle] pub extern "C" fn regorus_engine_get_packages(engine: *mut RegorusEngine) -> RegorusResult { to_regorus_string_result(|| -> Result { @@ -198,6 +197,16 @@ pub extern "C" fn regorus_engine_get_packages(engine: *mut RegorusEngine) -> Reg }()) } +/// Get list of policies as JSON. +/// +/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_policies +#[no_mangle] +pub extern "C" fn regorus_engine_get_policies(engine: *mut RegorusEngine) -> RegorusResult { + to_regorus_string_result(|| -> Result { + to_ref(&engine)?.engine.get_policies_as_json() + }()) +} + #[cfg(feature = "std")] #[no_mangle] pub extern "C" fn regorus_engine_add_data_from_json_file( diff --git a/bindings/go/main.go b/bindings/go/main.go index eb2ac321..a7efed17 100644 --- a/bindings/go/main.go +++ b/bindings/go/main.go @@ -82,6 +82,19 @@ func main() { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } + fmt.Printf("%s\n", output) + // Print packages + if output, err = engine1.GetPackages(); err != nil { + fmt.Fprintf(os.Stderr, "error: %v\n", err) + os.Exit(1) + } + fmt.Printf("%s\n", output) + + // Print policies + if output, err = engine1.GetPolicies(); err != nil { + fmt.Fprintf(os.Stderr, "error: %v\n", err) + os.Exit(1) + } fmt.Printf("%s\n", output) } diff --git a/bindings/go/pkg/regorus/mod.go b/bindings/go/pkg/regorus/mod.go index 55d7da6e..725f7b1a 100644 --- a/bindings/go/pkg/regorus/mod.go +++ b/bindings/go/pkg/regorus/mod.go @@ -55,6 +55,25 @@ func (e *Engine) AddPolicyFromFile(path string) (string, error) { return C.GoString(result.output), nil } +func (e *Engine) GetPackages() (string, error) { + result := C.regorus_engine_get_packages(e.e) + defer C.regorus_result_drop(result) + if result.status != C.RegorusStatusOk { + return "", fmt.Errorf("%s", C.GoString(result.error_message)) + } + return C.GoString(result.output), nil +} + +func (e *Engine) GetPolicies() (string, error) { + result := C.regorus_engine_get_policies(e.e) + defer C.regorus_result_drop(result) + if result.status != C.RegorusStatusOk { + return "", fmt.Errorf("%s", C.GoString(result.error_message)) + } + return C.GoString(result.output), nil +} + + func (e *Engine) AddDataJson(data string) error { data_c := C.CString(data) defer C.free(unsafe.Pointer(data_c)) diff --git a/bindings/java/Test.java b/bindings/java/Test.java index 3940f013..4f75f156 100644 --- a/bindings/java/Test.java +++ b/bindings/java/Test.java @@ -31,6 +31,12 @@ public static void main(String[] args) { System.out.println(coverageJson); System.out.println(engine.getCoverageReportPretty()); + + String packagesJson = engine.getPackages(); + System.out.println(packagesJson); + + String policiesJson = engine.getPolicies(); + System.out.println(policiesJson); } } } diff --git a/bindings/java/src/lib.rs b/bindings/java/src/lib.rs index 163d9c1d..748be7c5 100644 --- a/bindings/java/src/lib.rs +++ b/bindings/java/src/lib.rs @@ -89,6 +89,25 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeGetPackages( } } +#[no_mangle] +pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeGetPolicies( + env: JNIEnv, + _class: JClass, + engine_ptr: jlong, +) -> jstring { + let res = throw_err(env, |env| { + let engine = unsafe { &mut *(engine_ptr as *mut Engine) }; + let policies = engine.get_policies_as_json()?; + let policies_json = env.new_string(&policies)?; + Ok(policies_json.into_raw()) + }); + + match res { + Ok(val) => val, + Err(_) => JObject::null().into_raw(), + } +} + #[no_mangle] pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeClearData( env: JNIEnv, diff --git a/bindings/java/src/main/java/com/microsoft/regorus/Engine.java b/bindings/java/src/main/java/com/microsoft/regorus/Engine.java index 900ec96b..20a97277 100644 --- a/bindings/java/src/main/java/com/microsoft/regorus/Engine.java +++ b/bindings/java/src/main/java/com/microsoft/regorus/Engine.java @@ -26,6 +26,7 @@ public class Engine implements AutoCloseable, Cloneable { private static native String nativeAddPolicy(long enginePtr, String path, String rego); private static native String nativeAddPolicyFromFile(long enginePtr, String path); private static native String nativeGetPackages(long enginePtr); + private static native String nativeGetPolicies(long enginePtr); private static native void nativeClearData(long enginePtr); private static native void nativeAddDataJson(long enginePtr, String data); private static native void nativeAddDataJsonFromFile(long enginePtr, String path); @@ -96,6 +97,15 @@ public String getPackages() { return nativeGetPackages(enginePtr); } + /** + * Get list of loaded policies. + * + * @return List of Rego policies as a JSON array of sources. + */ + public String getPolicies() { + return nativeGetPolicies(enginePtr); + } + /** * Clears the data document. */ diff --git a/bindings/python/src/lib.rs b/bindings/python/src/lib.rs index 7454202d..dbffb05d 100644 --- a/bindings/python/src/lib.rs +++ b/bindings/python/src/lib.rs @@ -184,10 +184,18 @@ impl Engine { /// Get the list of packages defined by loaded policies. /// - pub fn get_packages(&mut self) -> Result> { + pub fn get_packages(&self) -> Result> { self.engine.get_packages() } + /// Get the list of policies. + /// + pub fn get_policies(&self) -> Result { + Ok(serde_json::to_string_pretty( + &self.engine.get_policies_as_json()?, + )?) + } + /// Add policy data. /// /// * `data`: Rego value. A Rego value is a number, bool, string, None diff --git a/bindings/ruby/ext/regorusrb/src/lib.rs b/bindings/ruby/ext/regorusrb/src/lib.rs index d1e042fb..510437f9 100644 --- a/bindings/ruby/ext/regorusrb/src/lib.rs +++ b/bindings/ruby/ext/regorusrb/src/lib.rs @@ -92,6 +92,20 @@ impl Engine { Ok(()) } + fn get_packages(&self) -> Result, Error> { + self.engine + .borrow() + .get_packages() + .map_err(|e| Error::new(runtime_error(), format!("Failed to get packages: {e}"))) + } + + fn get_policies(&self) -> Result { + self.engine + .borrow() + .get_policies_as_json() + .map_err(|e| Error::new(runtime_error(), format!("Failed to get policies: {e}"))) + } + fn set_input(&self, ruby_hash: magnus::RHash) -> Result<(), Error> { let input_value: regorus::Value = serde_magnus::deserialize(ruby_hash).map_err(|e| { Error::new( @@ -289,6 +303,8 @@ fn init(ruby: &Ruby) -> Result<(), Error> { "add_policy_from_file", method!(Engine::add_policy_from_file, 1), )?; + engine_class.define_method("get_packages", method!(Engine::get_packages, 0))?; + engine_class.define_method("get_policies", method!(Engine::get_policies, 0))?; // data operations engine_class.define_method("add_data", method!(Engine::add_data, 1))?; diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index 9a03ff00..27b35eb8 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -72,6 +72,13 @@ impl Engine { self.engine.get_packages().map_err(error_to_jsvalue) } + /// Get the list of policies. + /// + /// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_policies + pub fn getPolicies(&self) -> Result { + self.engine.get_policies_as_json().map_err(error_to_jsvalue) + } + /// Clear policy data. /// /// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.clear_data @@ -179,6 +186,7 @@ impl Engine { #[cfg(test)] mod tests { + use crate::error_to_jsvalue; use wasm_bindgen::prelude::*; use wasm_bindgen_test::wasm_bindgen_test; @@ -216,7 +224,7 @@ mod tests { assert_eq!(pkg, "data.test"); let results = engine.evalQuery("data".to_string())?; - let r = regorus::Value::from_json_str(&results).map_err(crate::error_to_jsvalue)?; + let r = regorus::Value::from_json_str(&results).map_err(error_to_jsvalue)?; let v = &r["result"][0]["expressions"][0]["value"]; @@ -228,7 +236,7 @@ mod tests { // Use eval_rule to perform same query. let v = engine.evalRule("data.test.message".to_owned())?; - let v = regorus::Value::from_json_str(&v).map_err(crate::error_to_jsvalue)?; + let v = regorus::Value::from_json_str(&v).map_err(error_to_jsvalue)?; // Ensure that input and policy were evaluated. assert_eq!(v, regorus::Value::from("Hello")); @@ -246,7 +254,7 @@ mod tests { // Test code coverage. let report = engine1.getCoverageReport()?; - let r = regorus::Value::from_json_str(&report).map_err(crate::error_to_jsvalue)?; + let r = regorus::Value::from_json_str(&report).map_err(error_to_jsvalue)?; assert_eq!( r["files"][0]["covered"] @@ -258,6 +266,13 @@ mod tests { println!("{}", engine1.getCoverageReportPretty()?); engine1.clearCoverageData(); + + let policies = engine1.getPolicies()?; + let v = regorus::Value::from_json_str(&policies).map_err(error_to_jsvalue)?; + assert_eq!( + v[0]["path"].as_string().map_err(error_to_jsvalue)?.as_ref(), + "hello.rego" + ); Ok(()) } } diff --git a/src/engine.rs b/src/engine.rs index a0067cdb..3a31b703 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -129,6 +129,66 @@ impl Engine { .collect() } + /// Get the list of policy files. + /// ``` + /// # use regorus::*; + /// # fn main() -> anyhow::Result<()> { + /// # let mut engine = Engine::new(); + /// + /// let pkg = engine.add_policy("hello.rego".to_string(), "package test".to_string())?; + /// assert_eq!(pkg, "data.test"); + /// + /// let policies = engine.get_policies()?; + /// + /// assert_eq!(policies[0].get_path(), "hello.rego"); + /// assert_eq!(policies[0].get_contents(), "package test"); + /// # Ok(()) + /// # } + /// ``` + pub fn get_policies(&self) -> Result> { + Ok(self + .modules + .iter() + .map(|m| m.package.refr.span().source.clone()) + .collect()) + } + + /// Get the list of policy files as a JSON object. + /// ``` + /// # use regorus::*; + /// # fn main() -> anyhow::Result<()> { + /// # let mut engine = Engine::new(); + /// + /// let pkg = engine.add_policy("hello.rego".to_string(), "package test".to_string())?; + /// assert_eq!(pkg, "data.test"); + /// + /// let policies = engine.get_policies_as_json()?; + /// + /// let v = Value::from_json_str(&policies)?; + /// assert_eq!(v[0]["path"].as_string()?.as_ref(), "hello.rego"); + /// assert_eq!(v[0]["contents"].as_string()?.as_ref(), "package test"); + /// # Ok(()) + /// # } + /// ``` + pub fn get_policies_as_json(&self) -> Result { + #[derive(Serialize)] + struct Source<'a> { + path: &'a String, + contents: &'a String, + } + + let mut sources = vec![]; + for m in self.modules.iter() { + let source = &m.package.refr.span().source; + sources.push(Source { + path: source.get_path(), + contents: source.get_contents(), + }); + } + + serde_json::to_string_pretty(&sources).map_err(anyhow::Error::msg) + } + /// Set the input document. /// /// * `input`: Input documented. Typically this [Value] is constructed from JSON or YAML. diff --git a/src/lexer.rs b/src/lexer.rs index 28faa07e..5888300f 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -20,6 +20,7 @@ struct SourceInternal { pub lines: Vec<(u32, u32)>, } +/// A policy file. #[derive(Clone)] #[cfg_attr(feature = "ast", derive(serde::Serialize))] pub struct Source { @@ -27,6 +28,18 @@ pub struct Source { src: Rc, } +impl Source { + /// The path associated with the policy file. + pub fn get_path(&self) -> &String { + &self.src.file + } + + /// The contents of the policy file. + pub fn get_contents(&self) -> &String { + &self.src.contents + } +} + impl cmp::Ord for Source { fn cmp(&self, other: &Source) -> cmp::Ordering { Rc::as_ptr(&self.src).cmp(&Rc::as_ptr(&other.src)) diff --git a/src/lib.rs b/src/lib.rs index 58e522f0..39f84b2e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,7 @@ mod utils; mod value; pub use engine::Engine; +pub use lexer::Source; pub use value::Value; #[cfg(feature = "arc")]