regorus/
compiled_policy.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use crate::ast::*;
5use crate::engine::Engine;
6use crate::scheduler::*;
7use crate::utils::*;
8use crate::*;
9
10use alloc::collections::BTreeMap;
11use anyhow::Result;
12
13#[cfg(feature = "azure_policy")]
14use crate::target::Target;
15
16pub(crate) type DefaultRuleInfo = (Ref<Rule>, Option<crate::String>);
17
18#[cfg(feature = "azure_policy")]
19pub(crate) type ResourceTypeInfo = (Rc<str>, Rc<Schema>);
20
21#[cfg(feature = "azure_policy")]
22pub(crate) type InferredResourceTypes = BTreeMap<Ref<Query>, ResourceTypeInfo>;
23
24/// Wrapper around CompiledPolicyData that holds an Rc reference.
25#[derive(Debug, Clone)]
26pub struct CompiledPolicy {
27    inner: Rc<CompiledPolicyData>,
28}
29
30impl CompiledPolicy {
31    /// Create a new CompiledPolicy from CompiledPolicyData.
32    pub(crate) fn new(inner: Rc<CompiledPolicyData>) -> Self {
33        Self { inner }
34    }
35}
36
37impl CompiledPolicy {
38    /// Evaluate the compiled policy with the given input.
39    ///
40    /// For target policies, evaluates the target's effect rule.
41    /// For regular policies, evaluates the originally compiled rule.
42    ///
43    /// * `input`: Input data (resource) to validate against the policy.
44    ///
45    /// Returns the result of evaluating the rule.
46    pub fn eval_with_input(&self, input: Value) -> Result<Value> {
47        let mut engine = Engine::new_from_compiled_policy(self.inner.clone());
48
49        // Set input
50        engine.set_input(input);
51
52        // Evaluate the rule
53        #[cfg(feature = "azure_policy")]
54        if let Some(target_info) = self.inner.target_info.as_ref() {
55            return engine.eval_rule(target_info.effect_path.to_string());
56        }
57        engine.eval_rule(self.inner.rule_to_evaluate.to_string())
58    }
59
60    /// Get information about the compiled policy including metadata about modules,
61    /// target configuration, and resource types.
62    ///
63    /// Returns a [`crate::policy_info::PolicyInfo`] struct containing comprehensive
64    /// information about the compiled policy such as module IDs, target name,
65    /// applicable resource types, entry point rule, and parameters.
66    ///
67    /// # Examples
68    ///
69    /// ```no_run
70    /// use regorus::*;
71    /// # use std::sync::Arc;
72    ///
73    /// # fn main() -> anyhow::Result<()> {
74    /// # // Register a target for the example
75    /// # #[cfg(feature = "azure_policy")]
76    /// # {
77    /// #    let target = regorus::target::Target::from_json_file("tests/interpreter/cases/target/definitions/sample_target.json")?;
78    /// #    regorus::registry::targets::register(std::sync::Arc::new(target))?;
79    /// # }
80    ///
81    /// // Compile the policy
82    /// let policy_rego = r#"
83    ///     package policy.example
84    ///     import rego.v1
85    ///     __target__ := "target.tests.sample_test_target"
86    ///     
87    ///     effect := "allow" if {
88    ///         input.type == "storage_account"
89    ///         input.location in ["eastus", "westus"]
90    ///     }
91    /// "#;
92    ///
93    /// let modules = vec![regorus::PolicyModule {
94    ///     id: "policy.rego".into(),
95    ///     content: policy_rego.into(),
96    /// }];
97    ///
98    /// #[cfg(feature = "azure_policy")]
99    /// let compiled = regorus::compile_policy_for_target(Value::new_object(), &modules)?;
100    /// #[cfg(not(feature = "azure_policy"))]
101    /// let compiled = regorus::compile_policy_with_entrypoint(Value::new_object(), &modules, "allow".into())?;
102    /// let info = compiled.get_policy_info()?;
103    ///
104    /// assert_eq!(info.target_name, Some("target.tests.sample_test_target".into()));
105    /// assert_eq!(info.effect_rule, Some("effect".into()));
106    /// assert!(info.module_ids.len() > 0);
107    /// # Ok(())
108    /// # }
109    /// ```
110    pub fn get_policy_info(&self) -> Result<crate::policy_info::PolicyInfo> {
111        // Extract module IDs from the compiled policy
112        let module_ids: Vec<Rc<str>> = self
113            .inner
114            .modules
115            .iter()
116            .enumerate()
117            .map(|(i, module)| {
118                // Use source file path if available, otherwise generate an ID
119                let source_path = module.package.span.source.get_path();
120                if source_path.is_empty() {
121                    format!("module_{}", i).into()
122                } else {
123                    source_path.clone().into()
124                }
125            })
126            .collect();
127
128        // Extract target name and effect rule
129        #[cfg(feature = "azure_policy")]
130        let (target_name, effect_rule) = if let Some(target_info) = &self.inner.target_info {
131            (
132                Some(target_info.target.name.clone()),
133                Some(target_info.effect_name.clone()),
134            )
135        } else {
136            (None, None)
137        };
138
139        #[cfg(not(feature = "azure_policy"))]
140        let (target_name, effect_rule) = (None, None);
141
142        // Extract applicable resource types from inferred types
143        #[cfg(feature = "azure_policy")]
144        let applicable_resource_types: Vec<Rc<str>> =
145            if let Some(inferred_types) = &self.inner.inferred_resource_types {
146                inferred_types
147                    .values()
148                    .map(|(resource_type, _schema)| resource_type.clone())
149                    .collect::<std::collections::BTreeSet<_>>() // Remove duplicates
150                    .into_iter()
151                    .collect()
152            } else {
153                Vec::new()
154            };
155
156        #[cfg(not(feature = "azure_policy"))]
157        let applicable_resource_types: Vec<Rc<str>> = Vec::new();
158
159        // Get parameters from the modules
160        #[cfg(feature = "azure_policy")]
161        let parameters = {
162            // Create a new engine from the compiled modules to extract parameters
163            let temp_engine = crate::engine::Engine::new_from_compiled_policy(self.inner.clone());
164
165            temp_engine.get_policy_parameters()?
166        };
167
168        Ok(crate::policy_info::PolicyInfo {
169            module_ids,
170            target_name,
171            applicable_resource_types,
172            entrypoint_rule: self.inner.rule_to_evaluate.clone(),
173            effect_rule,
174            #[cfg(feature = "azure_policy")]
175            parameters,
176        })
177    }
178}
179
180#[cfg(feature = "azure_policy")]
181#[derive(Debug, Clone)]
182pub(crate) struct TargetInfo {
183    pub(crate) target: Rc<Target>,
184    pub(crate) package: String,
185    pub(crate) effect_schema: Rc<Schema>,
186    pub(crate) effect_name: Rc<str>,
187    pub(crate) effect_path: Rc<str>,
188}
189
190#[derive(Debug, Clone, Default)]
191pub(crate) struct CompiledPolicyData {
192    pub(crate) modules: Rc<Vec<Ref<Module>>>,
193    pub(crate) schedule: Option<Schedule>,
194    pub(crate) rules: Map<String, Vec<Ref<Rule>>>,
195    pub(crate) default_rules: Map<String, Vec<DefaultRuleInfo>>,
196    pub(crate) imports: BTreeMap<String, Ref<Expr>>,
197    pub(crate) functions: FunctionTable,
198    pub(crate) rule_paths: Set<String>,
199    #[cfg(feature = "azure_policy")]
200    pub(crate) target_info: Option<TargetInfo>,
201    #[cfg(feature = "azure_policy")]
202    pub(crate) inferred_resource_types: Option<InferredResourceTypes>,
203
204    // User-defined rule to evaluate
205    pub(crate) rule_to_evaluate: Rc<str>,
206
207    // User-defined data
208    pub(crate) data: Option<Value>,
209
210    // Evaluation settings
211    pub(crate) strict_builtin_errors: bool,
212
213    // The semantics of extensions ought to be changes to be more Clone friendly.
214    pub(crate) extensions: Map<String, (u8, Rc<Box<dyn Extension>>)>,
215}