feat: Implement methods to get package names and parameters (#425)

This commit is contained in:
Denis Komissarov
2025-08-04 13:02:22 -07:00
committed by GitHub
parent 3b802c14cb
commit 9fce2ccc00
10 changed files with 335 additions and 3 deletions

5
.gitignore vendored
View File

@@ -27,4 +27,7 @@ bindings/*/target
# C# build folders
**bin
**obj
**obj
# Visual Studio folders
**/*.vs

View File

@@ -23,6 +23,7 @@ default = ["full-opa", "arc"]
arc = ["scientific/arc"]
ast = []
azure_policy = []
base64 = ["dep:data-encoding"]
base64url = ["dep:data-encoding"]
coverage = []

View File

@@ -173,4 +173,43 @@ public class RegorusTests
Assert.IsTrue(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(result!)), $"Actual: {result}");
}
[TestMethod]
public void GetPolicyPackageNames_succeeds()
{
using var engine = new Engine();
engine.AddPolicy(
"test.rego",
"package test\nx = 1\nmessage = `Hello`");
engine.AddPolicy(
"test.rego",
"package test.nested.name\nx = 1\nmessage = `Hello`");
var result = engine.GetPolicyPackageNames();
var packageNames = JsonNode.Parse(result!);
Assert.AreEqual("test", packageNames![0]["package_name"].ToString());
Assert.AreEqual("test.nested.name", packageNames![1]["package_name"].ToString());
}
[TestMethod]
public void GetPolicyParameters_succeeds()
{
using var engine = new Engine();
engine.AddPolicy(
"test.rego",
"package test\n default parameters.a = 5\nparameters.b = 10\nx = 1\nmessage = `Hello`");
var result = engine.GetPolicyParameters();
var parameters = JsonNode.Parse(result!);
Assert.AreEqual(1, parameters![0]["parameters"].AsArray().Count);
Assert.AreEqual(1, parameters![0]["modifiers"].AsArray().Count);
Assert.AreEqual("a", parameters![0]["parameters"][0]["name"].ToString());
Assert.AreEqual("b", parameters![0]["modifiers"][0]["name"].ToString());
}
}

View File

@@ -217,6 +217,16 @@ namespace Regorus
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_ast_as_json(E));
}
public string? GetPolicyPackageNames()
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_policy_package_names(E));
}
public string? GetPolicyParameters()
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_policy_parameters(E));
}
string? StringFromUTF8(IntPtr ptr)
{

View File

@@ -193,6 +193,22 @@ namespace Regorus.Internal
[DllImport(__DllName, EntryPoint = "regorus_engine_get_ast_as_json", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_get_ast_as_json(RegorusEngine* engine);
/// <summary>
/// Gets the package names of policies added to the engine.
///
/// See https://docs.rs/regorus/latest/regorus/coverage/struct.Engine.html#method.get_policy_package_names
/// </summary>
[DllImport(__DllName, EntryPoint = "regorus_engine_get_policy_package_names", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_get_policy_package_names(RegorusEngine* engine);
/// <summary>
/// Gets the parameters defined in each policy added to the engine
///
/// See https://docs.rs/regorus/latest/regorus/coverage/struct.Engine.html#method.get_policy_parameters
/// </summary>
[DllImport(__DllName, EntryPoint = "regorus_engine_get_policy_parameters", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_get_policy_parameters(RegorusEngine* engine);
/// <summary>
/// Enable/disable rego v1.
///

View File

@@ -4,7 +4,7 @@
},
"sdk": {
"allowPrerelease": false,
"version": "8.0.408",
"version": "8.0.412",
"rollForward": "disable"
}
}

View File

@@ -23,8 +23,9 @@ lto = true
codegen-units = 1
[features]
default = ["ast", "std", "coverage", "regorus/arc", "regorus/full-opa"]
default = ["ast", "azure_policy", "std", "coverage", "regorus/arc", "regorus/full-opa"]
ast = ["regorus/ast"]
azure_policy = ["regorus/azure_policy"]
std = ["regorus/std"]
coverage = ["regorus/coverage"]
custom_allocator = []

View File

@@ -457,6 +457,40 @@ pub extern "C" fn regorus_engine_get_ast_as_json(engine: *mut RegorusEngine) ->
}
}
/// Gets the package names defined in each policy added to the engine.
///
/// See https://docs.rs/regorus/latest/regorus/coverage/struct.Engine.html#method.get_policy_package_names
#[no_mangle]
#[cfg(feature = "azure_policy")]
pub extern "C" fn regorus_engine_get_policy_package_names(engine: *mut RegorusEngine) -> RegorusResult {
let output = || -> Result<String> { serde_json::to_string_pretty(&to_ref(engine)?.engine.get_policy_package_names()?).map_err(anyhow::Error::msg) }();
match output {
Ok(out) => RegorusResult {
status: RegorusStatus::RegorusStatusOk,
output: to_c_str(out),
error_message: std::ptr::null_mut(),
},
Err(e) => to_regorus_result(Err(e)),
}
}
/// Gets the parameters defined in each policy added to the engine.
///
/// See https://docs.rs/regorus/latest/regorus/coverage/struct.Engine.html#method.get_policy_parameters
#[no_mangle]
#[cfg(feature = "azure_policy")]
pub extern "C" fn regorus_engine_get_policy_parameters(engine: *mut RegorusEngine) -> RegorusResult {
let output = || -> Result<String> { serde_json::to_string_pretty(&to_ref(engine)?.engine.get_policy_parameters()?).map_err(anyhow::Error::msg) }();
match output {
Ok(out) => RegorusResult {
status: RegorusStatus::RegorusStatusOk,
output: to_c_str(out),
error_message: std::ptr::null_mut(),
},
Err(e) => to_regorus_result(Err(e)),
}
}
/// Enable/disable rego v1.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_rego_v0

View File

@@ -23,6 +23,31 @@ pub struct Engine {
rego_v1: bool,
}
#[derive(Debug, Clone, Serialize)]
pub struct PolicyPackageNameDefinition {
pub source_file: String,
pub package_name: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct PolicyParameter {
pub name: String,
pub modifiable: bool,
pub required: bool,
}
#[derive(Debug, Clone, Serialize)]
pub struct PolicyModifier {
pub name: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct PolicyParameters {
pub source_file: String,
pub parameters: Vec<PolicyParameter>,
pub modifiers: Vec<PolicyModifier>,
}
/// Create a default engine.
impl Default for Engine {
fn default() -> Self {
@@ -907,6 +932,113 @@ impl Engine {
serde_json::to_string_pretty(&ast).map_err(anyhow::Error::msg)
}
/// Get the package names of each policy added to the engine.
///
///
/// ```rust
/// # use regorus::*;
/// # use anyhow::{bail, Result};
/// # fn main() -> Result<()> {
/// # let mut engine = Engine::new();
/// engine.add_policy("test.rego".to_string(), "package test\n x := 1".to_string())?;
/// engine.add_policy("test2.rego".to_string(), "package test.multi.segment\n x := 1".to_string())?;
///
/// let package_names = engine.get_policy_package_names()?;
///
/// assert_eq!("test", package_names[0].package_name);
/// assert_eq!("test.multi.segment", package_names[1].package_name);
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "azure_policy")]
#[cfg_attr(docsrs, doc(cfg(feature = "azure_policy")))]
pub fn get_policy_package_names(&self) -> Result<Vec<PolicyPackageNameDefinition>> {
let mut package_names = vec![];
for m in &self.modules {
let package_name = Interpreter::get_path_string(&m.package.refr, None)?;
package_names.push(PolicyPackageNameDefinition {
source_file: m.package.span.source.file().to_string(),
package_name,
});
}
Ok(package_names)
}
/// Get the parameters defined in each policy.
///
///
/// ```rust
/// # use regorus::*;
/// # use anyhow::{bail, Result};
/// # fn main() -> Result<()> {
/// # let mut engine = Engine::new();
/// engine.add_policy("test.rego".to_string(), "package test default parameters.a = 5 parameters.b = 10\n x := 1".to_string())?;
///
/// let parameters = engine.get_policy_parameters()?;
///
/// assert_eq!("a", parameters[0].parameters[0].name);
/// assert_eq!("b", parameters[0].modifiers[0].name);
///
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "azure_policy")]
#[cfg_attr(docsrs, doc(cfg(feature = "azure_policy")))]
pub fn get_policy_parameters(&self) -> Result<Vec<PolicyParameters>> {
let mut policy_parameter_definitions = vec![];
for m in &self.modules {
let mut parameters = vec![];
let mut modifiers = vec![];
for rule in &m.policy {
// Extract parameter definitions from the policy rule
// e.g. default parameters.a = 5
if let Rule::Default { refr, .. } = rule.as_ref() {
let path = Parser::get_path_ref_components(refr)?;
let paths: Vec<&str> = path.iter().map(|s| s.text()).collect();
if paths.len() == 2 && paths[0] == "parameters" {
// Todo: Fetch fields other than name from rego metadoc for the parameter
parameters.push(PolicyParameter {
name: paths[1].to_string(),
modifiable: false,
required: false,
})
}
}
// Extract modifiers to the parameters from the policy rule
// e.g. parameters.a = 5
if let Rule::Spec { head, .. } = rule.as_ref() {
match head {
RuleHead::Compr { refr, .. } => {
let path = Parser::get_path_ref_components(refr)?;
let paths: Vec<&str> = path.iter().map(|s| s.text()).collect();
if paths.len() == 2 && paths[0] == "parameters" {
// Todo: Fetch fields other than name from rego metadoc for the parameter
modifiers.push(PolicyModifier {
name: paths[1].to_string(),
})
}
}
RuleHead::Func { .. } => {}
RuleHead::Set { .. } => {}
}
}
}
policy_parameter_definitions.push(PolicyParameters {
source_file: m.package.span.source.file().to_string(),
parameters,
modifiers,
});
}
Ok(policy_parameter_definitions)
}
fn make_parser<'a>(&self, source: &'a Source) -> Result<Parser<'a>> {
let mut parser = Parser::new(source)?;
if self.rego_v1 {

View File

@@ -101,3 +101,99 @@ fn extension_with_state() -> Result<()> {
Ok(())
}
#[test]
#[cfg(feature = "azure_policy")]
#[cfg_attr(docsrs, doc(cfg(feature = "azure_policy")))]
fn get_policy_package_names() -> Result<()> {
let mut engine = Engine::new();
engine.add_policy(
"testPolicy1".to_string(),
r#"package test
deny if {
1 == 2
}
"#
.to_string(),
)?;
engine.add_policy(
"testPolicy2".to_string(),
r#"package test.nested.name
deny if {
1 == 2
}
"#
.to_string(),
)?;
let package_names = engine.get_policy_package_names()?;
assert_eq!(2, package_names.len());
assert_eq!("test", package_names[0].package_name);
assert_eq!("testPolicy1", package_names[0].source_file);
assert_eq!("test.nested.name", package_names[1].package_name);
assert_eq!("testPolicy2", package_names[1].source_file);
Ok(())
}
#[test]
#[cfg(feature = "azure_policy")]
#[cfg_attr(docsrs, doc(cfg(feature = "azure_policy")))]
fn get_policy_parameters() -> Result<()> {
let mut engine = Engine::new();
engine.add_policy(
"testPolicy1".to_string(),
r#"package test
default parameters.a = 5
default parameters.b = { asdf: 10}
parameters.c = 10
deny if {
parameter.a == parameter.b.asdf
}
"#
.to_string(),
)?;
engine.add_policy(
"testPolicy2".to_string(),
r#"package test
default parameters = {
a: 5,
b: { asdf: 10 }
}
parameters.c = 5
deny if {
parameters.a == parameters.b.asdf
}
"#
.to_string(),
)?;
let parameters = engine.get_policy_parameters()?;
// let ast = engine.get_ast_as_json()?;
// println!("ast: {}", ast);
// let parameters = Value::from_json_str(&result)?;
assert_eq!(2, parameters.len());
let test_policy1_parameters = &parameters[0];
assert_eq!(2, test_policy1_parameters.parameters.len());
assert_eq!("a", test_policy1_parameters.parameters[0].name);
assert_eq!("b", test_policy1_parameters.parameters[1].name);
// We expect parameters to be defined separately, so the second policy does not have any parameters
let test_policy2_parameters = &parameters[1];
assert_eq!(0, test_policy2_parameters.parameters.len());
assert_eq!(1, test_policy2_parameters.modifiers.len());
assert_eq!("c", test_policy2_parameters.modifiers[0].name);
Ok(())
}