Skip to main content

regorus/
engine.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3#![allow(clippy::print_stderr)]
4
5use crate::{
6    ast::*,
7    compiled_policy::CompiledPolicy,
8    interpreter::*,
9    lexer::*,
10    parser::*,
11    scheduler::*,
12    utils::{
13        gather_functions,
14        limits::{self, fallback_execution_timer_config, ExecutionTimerConfig},
15    },
16    value::*,
17    Extension, QueryResults, *,
18};
19
20use crate::Rc;
21use anyhow::{anyhow, bail, Result};
22
23#[cfg(feature = "newton-identity")]
24use crate::extensions::identity::KycIdentityData;
25
26/// The Rego evaluation engine.
27#[derive(Debug, Clone)]
28pub struct Engine {
29    modules: Rc<Vec<Ref<Module>>>,
30    interpreter: Interpreter,
31    prepared: bool,
32    rego_v1: bool,
33    execution_timer_config: Option<ExecutionTimerConfig>,
34}
35
36#[cfg(feature = "azure_policy")]
37#[derive(Debug, Clone, Serialize)]
38pub struct PolicyPackageNameDefinition {
39    pub source_file: String,
40    pub package_name: String,
41}
42
43#[cfg(feature = "azure_policy")]
44#[derive(Debug, Clone, Serialize)]
45pub struct PolicyParameter {
46    pub name: String,
47    pub modifiable: bool,
48    pub required: bool,
49}
50
51#[cfg(feature = "azure_policy")]
52#[derive(Debug, Clone, Serialize)]
53pub struct PolicyModifier {
54    pub name: String,
55}
56
57#[cfg(feature = "azure_policy")]
58#[derive(Debug, Clone, Serialize)]
59pub struct PolicyParameters {
60    pub source_file: String,
61    pub parameters: Vec<PolicyParameter>,
62    pub modifiers: Vec<PolicyModifier>,
63}
64
65/// Create a default engine.
66impl Default for Engine {
67    fn default() -> Self {
68        Self::new()
69    }
70}
71
72impl Engine {
73    fn effective_execution_timer_config(&self) -> Option<ExecutionTimerConfig> {
74        self.execution_timer_config
75            .or_else(fallback_execution_timer_config)
76    }
77
78    fn apply_effective_execution_timer_config(&mut self) {
79        let config = self.effective_execution_timer_config();
80        self.interpreter.set_execution_timer_config(config);
81    }
82
83    /// Create an instance of [Engine].
84    pub fn new() -> Self {
85        let mut engine = Self {
86            modules: Rc::new(vec![]),
87            interpreter: Interpreter::new(),
88            prepared: false,
89            rego_v1: true,
90            execution_timer_config: None,
91        };
92        engine.apply_effective_execution_timer_config();
93        engine
94    }
95
96    /// Enable rego v0.
97    ///
98    /// Note that regorus now defaults to v1.
99    /// ```
100    /// # use regorus::*;
101    /// # fn main() -> anyhow::Result<()> {
102    /// let mut engine = Engine::new();
103    ///
104    /// // Enable v0 for old style policies.
105    /// engine.set_rego_v0(true);
106    ///
107    /// engine.add_policy(
108    ///     "test.rego".to_string(),
109    ///     r#"
110    ///    package test
111    ///
112    ///    allow { # v0 syntax does not require if keyword
113    ///       1 < 2
114    ///    }
115    ///    "#
116    ///     .to_string(),
117    /// )?;
118    ///
119    /// # Ok(())
120    /// # }
121    /// ```
122    pub const fn set_rego_v0(&mut self, rego_v0: bool) {
123        self.rego_v1 = !rego_v0;
124    }
125
126    /// Configure the execution timer.
127    ///
128    /// Stores the supplied configuration and ensures the next evaluation is checked against those
129    /// limits. Engines start without a time limit and otherwise fall back to the global
130    /// configuration (if provided).
131    ///
132    /// # Examples
133    ///
134    /// ```
135    /// use regorus::{utils::limits::ExecutionTimerConfig, Engine};
136    /// use std::{num::NonZeroU32, time::Duration};
137    ///
138    /// let mut engine = Engine::new();
139    /// let config = ExecutionTimerConfig {
140    ///     limit: Duration::from_millis(10),
141    ///     check_interval: NonZeroU32::new(1).unwrap(),
142    /// };
143    ///
144    /// engine.set_execution_timer_config(config);
145    /// ```
146    pub fn set_execution_timer_config(&mut self, config: ExecutionTimerConfig) {
147        self.execution_timer_config = Some(config);
148        self.interpreter.set_execution_timer_config(Some(config));
149    }
150
151    /// Clear the engine-specific execution timer configuration, falling back to the global value.
152    ///
153    /// # Examples
154    ///
155    /// ```
156    /// use regorus::{
157    ///     utils::limits::{set_fallback_execution_timer_config, ExecutionTimerConfig},
158    ///     Engine,
159    /// };
160    /// use std::{num::NonZeroU32, time::Duration};
161    ///
162    /// let mut engine = Engine::new();
163    /// let global = ExecutionTimerConfig {
164    ///     limit: Duration::from_millis(5),
165    ///     check_interval: NonZeroU32::new(1).unwrap(),
166    /// };
167    /// set_fallback_execution_timer_config(Some(global));
168    ///
169    /// engine.clear_execution_timer_config();
170    /// ```
171    pub fn clear_execution_timer_config(&mut self) {
172        self.execution_timer_config = None;
173        self.apply_effective_execution_timer_config();
174    }
175
176    /// Register Newton crypto extensions with the engine.
177    ///
178    /// This method registers Newton-specific Rego built-in functions for
179    /// Ethereum cryptography operations, including ECDSA signature recovery.
180    ///
181    /// Available functions:
182    /// - `newton.crypto.ecdsa_recover_signer(signature, message_hash)` - Recover signer from raw hash
183    /// - `newton.crypto.ecdsa_recover_signer_personal(signature, message)` - Recover signer using personal_sign format
184    ///
185    /// ```
186    /// # use regorus::*;
187    /// # fn main() -> anyhow::Result<()> {
188    /// let mut engine = Engine::new();
189    ///
190    /// // Register Newton crypto extensions
191    /// engine.with_newton_crypto_extensions()?;
192    ///
193    /// engine.add_policy(
194    ///     "test.rego".to_string(),
195    ///     r#"
196    ///    package test
197    ///    signer := newton.crypto.ecdsa_recover_signer(input.signature, input.hash)
198    ///    "#
199    ///     .to_string(),
200    /// )?;
201    /// # Ok(())
202    /// # }
203    /// ```
204    #[cfg(feature = "newton-crypto")]
205    #[cfg_attr(docsrs, doc(cfg(feature = "newton-crypto")))]
206    pub fn with_newton_crypto_extensions(&mut self) -> Result<()> {
207        crate::extensions::crypto::register_newton_crypto_extensions(self)
208    }
209
210    /// Register Newton KYC identity domain extensions with the engine.
211    ///
212    /// Registers domain-namespaced built-ins under `newton.identity.kyc.*`
213    /// and the generic `newton.identity.get(field)` accessor. Pass `None`
214    /// for `existing_fields` when KYC is the first domain; pass the returned
215    /// handle to subsequent domain registrations so all domains share a
216    /// single `newton.identity.get` accessor.
217    ///
218    /// # Available functions
219    ///
220    /// | Function | Description |
221    /// |----------|-------------|
222    /// | `newton.identity.kyc.check_approved` | Check approval status |
223    /// | `newton.identity.kyc.address_in_countries` | Check document country against list |
224    /// | `newton.identity.kyc.address_in_subdivision` | Check document address against ISO subdivision list |
225    /// | `newton.identity.kyc.address_not_in_subdivision` | Check document address is not in ISO subdivision list |
226    /// | `newton.identity.kyc.age_gte` | Check birthdate implies age >= input |
227    /// | `newton.identity.kyc.not_expired` | Check document expiration date has not passed |
228    /// | `newton.identity.kyc.valid_for` | Check document is valid for N more days |
229    /// | `newton.identity.kyc.issued_since` | Check document was issued at least N days ago |
230    /// | `newton.identity.get(field)` | Generic accessor for any identity domain field |
231    ///
232    /// ```
233    /// # use regorus::*;
234    /// # use regorus::extensions::identity::KycIdentityData;
235    /// # fn main() -> anyhow::Result<()> {
236    /// let mut engine = Engine::new();
237    ///
238    /// let id = KycIdentityData {
239    ///     reference_date: "2026-01-01".to_string(),
240    ///     status: "approved".to_string(),
241    ///     selected_country_code: "US".to_string(),
242    ///     address_subdivision: "CA".to_string(),
243    ///     address_country_code: "US".to_string(),
244    ///     birthdate: "1970-01-01".to_string(),
245    ///     expiration_date: "2030-01-01".to_string(),
246    ///     issue_date: "2020-01-01".to_string(),
247    ///     issuing_authority: "CA".to_string(),
248    /// };
249    ///
250    /// // Register Newton KYC identity extensions (first domain, pass None)
251    /// let _shared = engine.with_newton_identity_kyc_extensions(id, None)?;
252    ///
253    /// engine.add_policy(
254    ///     "test.rego".to_string(),
255    ///     r#"
256    ///    package test
257    ///    allow if {
258    ///        newton.identity.kyc.check_approved()
259    ///        newton.identity.kyc.address_in_countries(["US"])
260    ///        newton.identity.kyc.address_not_in_subdivision(["NY","NC","HI"])
261    ///    }
262    ///    "#
263    ///     .to_string(),
264    /// )?;
265    /// # Ok(())
266    /// # }
267    /// ```
268    #[cfg(feature = "newton-identity")]
269    #[cfg_attr(docsrs, doc(cfg(feature = "newton-identity")))]
270    pub fn with_newton_identity_kyc_extensions(
271        &mut self,
272        data: KycIdentityData,
273        existing_fields: Option<crate::extensions::identity::SharedIdentityFields>,
274    ) -> Result<crate::extensions::identity::SharedIdentityFields> {
275        crate::extensions::identity::register_kyc_identity_extensions(self, data, existing_fields)
276    }
277
278    /// Register Newton TLSNotary extensions.
279    ///
280    /// This adds the `newton.crypto.tlsn_verify` built-in function that
281    /// verifies TLSNotary presentations against a trusted notary public key.
282    ///
283    /// ```no_run
284    /// # use regorus::Engine;
285    /// let mut engine = Engine::new();
286    /// engine.with_newton_tlsn_extensions().unwrap();
287    /// ```
288    #[cfg(feature = "newton-tlsn")]
289    #[cfg_attr(docsrs, doc(cfg(feature = "newton-tlsn")))]
290    pub fn with_newton_tlsn_extensions(&mut self) -> Result<()> {
291        crate::extensions::tlsn::register_newton_tlsn_extensions(self)
292    }
293
294    /// Add a policy.
295    ///
296    /// The policy file will be parsed and converted to AST representation.
297    /// Multiple policy files may be added to the engine.
298    /// Returns the Rego package name declared in the policy.
299    ///
300    /// * `path`: A filename to be associated with the policy.
301    /// * `rego`: The rego policy code.
302    ///
303    /// ```
304    /// # use regorus::*;
305    /// # fn main() -> anyhow::Result<()> {
306    /// let mut engine = Engine::new();
307    ///
308    /// let package = engine.add_policy(
309    ///     "test.rego".to_string(),
310    ///     r#"
311    ///    package test
312    ///    allow = input.user == "root"
313    ///    "#
314    ///     .to_string(),
315    /// )?;
316    ///
317    /// assert_eq!(package, "data.test");
318    /// # Ok(())
319    /// # }
320    /// ```
321    pub fn add_policy(&mut self, path: String, rego: String) -> Result<String> {
322        let source = Source::from_contents(path, rego)?;
323        let mut parser = self.make_parser(&source)?;
324        let module = Ref::new(parser.parse()?);
325        limits::enforce_memory_limit().map_err(|err| anyhow!(err))?;
326        Rc::make_mut(&mut self.modules).push(module.clone());
327        // if policies change, interpreter needs to be prepared again
328        self.prepared = false;
329        Interpreter::get_path_string(&module.package.refr, Some("data"))
330    }
331
332    /// Add a policy from a given file.
333    ///
334    /// The policy file will be parsed and converted to AST representation.
335    /// Multiple policy files may be added to the engine.
336    /// Returns the Rego package name declared in the policy.
337    ///
338    /// * `path`: Path to the policy file (.rego).
339    ///
340    /// ```
341    /// # use regorus::*;
342    /// # fn main() -> anyhow::Result<()> {
343    /// let mut engine = Engine::new();
344    /// // framework.rego does not conform to v1.
345    /// engine.set_rego_v0(true);
346    ///
347    /// let package = engine.add_policy_from_file("tests/aci/framework.rego")?;
348    ///
349    /// assert_eq!(package, "data.framework");
350    /// # Ok(())
351    /// # }
352    /// ```
353    #[cfg(feature = "std")]
354    #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
355    pub fn add_policy_from_file<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<String> {
356        let source = Source::from_file(path)?;
357        let mut parser = self.make_parser(&source)?;
358        let module = Ref::new(parser.parse()?);
359        limits::enforce_memory_limit().map_err(|err| anyhow!(err))?;
360        Rc::make_mut(&mut self.modules).push(module.clone());
361        // if policies change, interpreter needs to be prepared again
362        self.prepared = false;
363        Interpreter::get_path_string(&module.package.refr, Some("data"))
364    }
365
366    /// Get the list of packages defined by loaded policies.
367    ///
368    /// ```
369    /// # use regorus::*;
370    /// # fn main() -> anyhow::Result<()> {
371    /// let mut engine = Engine::new();
372    /// // framework.rego does not conform to v1.
373    /// engine.set_rego_v0(true);
374    ///
375    /// let _ = engine.add_policy_from_file("tests/aci/framework.rego")?;
376    ///
377    /// // Package names can be different from file names.
378    /// let _ = engine.add_policy("policy.rego".into(), "package hello.world".into())?;
379    ///
380    /// assert_eq!(
381    ///     engine.get_packages()?,
382    ///     vec!["data.framework", "data.hello.world"]
383    /// );
384    /// # Ok(())
385    /// # }
386    /// ```
387    pub fn get_packages(&self) -> Result<Vec<String>> {
388        self.modules
389            .iter()
390            .map(|m| Interpreter::get_path_string(&m.package.refr, Some("data")))
391            .collect()
392    }
393
394    /// Get the list of policy files.
395    /// ```
396    /// # use regorus::*;
397    /// # fn main() -> anyhow::Result<()> {
398    /// # let mut engine = Engine::new();
399    ///
400    /// let pkg = engine.add_policy("hello.rego".to_string(), "package test".to_string())?;
401    /// assert_eq!(pkg, "data.test");
402    ///
403    /// let policies = engine.get_policies()?;
404    ///
405    /// assert_eq!(policies[0].get_path(), "hello.rego");
406    /// assert_eq!(policies[0].get_contents(), "package test");
407    /// # Ok(())
408    /// # }
409    /// ```
410    pub fn get_policies(&self) -> Result<Vec<Source>> {
411        Ok(self
412            .modules
413            .iter()
414            .map(|m| m.package.refr.span().source.clone())
415            .collect())
416    }
417
418    /// Get the list of policy files as a JSON object.
419    /// ```
420    /// # use regorus::*;
421    /// # fn main() -> anyhow::Result<()> {
422    /// # let mut engine = Engine::new();
423    ///
424    /// let pkg = engine.add_policy("hello.rego".to_string(), "package test".to_string())?;
425    /// assert_eq!(pkg, "data.test");
426    ///
427    /// let policies = engine.get_policies_as_json()?;
428    ///
429    /// let v = Value::from_json_str(&policies)?;
430    /// assert_eq!(v[0]["path"].as_string()?.as_ref(), "hello.rego");
431    /// assert_eq!(v[0]["contents"].as_string()?.as_ref(), "package test");
432    /// # Ok(())
433    /// # }
434    /// ```
435    pub fn get_policies_as_json(&self) -> Result<String> {
436        #[derive(Serialize)]
437        struct Source<'a> {
438            path: &'a String,
439            contents: &'a String,
440        }
441
442        let mut sources = vec![];
443        for m in self.modules.iter() {
444            let source = &m.package.refr.span().source;
445            sources.push(Source {
446                path: source.get_path(),
447                contents: source.get_contents(),
448            });
449        }
450
451        serde_json::to_string_pretty(&sources).map_err(anyhow::Error::msg)
452    }
453
454    /// Set the input document.
455    ///
456    /// * `input`: Input documented. Typically this [Value] is constructed from JSON or YAML.
457    ///
458    /// ```
459    /// # use regorus::*;
460    /// # fn main() -> anyhow::Result<()> {
461    /// let mut engine = Engine::new();
462    ///
463    /// let input = Value::from_json_str(
464    ///     r#"
465    /// {
466    ///   "role" : "admin",
467    ///   "action": "delete"
468    /// }"#,
469    /// )?;
470    ///
471    /// engine.set_input(input);
472    /// # Ok(())
473    /// # }
474    /// ```
475    pub fn set_input(&mut self, input: Value) {
476        self.interpreter.set_input(input);
477    }
478
479    pub fn set_input_json(&mut self, input_json: &str) -> Result<()> {
480        self.set_input(Value::from_json_str(input_json)?);
481        Ok(())
482    }
483
484    /// Clear the data document.
485    ///
486    /// The data document will be reset to an empty object.
487    ///
488    /// ```
489    /// # use regorus::*;
490    /// # fn main() -> anyhow::Result<()> {
491    /// let mut engine = Engine::new();
492    ///
493    /// engine.clear_data();
494    ///
495    /// // Evaluate data.
496    /// let results = engine.eval_query("data".to_string(), false)?;
497    ///
498    /// // Assert that it is empty object.
499    /// assert_eq!(results.result.len(), 1);
500    /// assert_eq!(results.result[0].expressions.len(), 1);
501    /// assert_eq!(results.result[0].expressions[0].value, Value::new_object());
502    /// # Ok(())
503    /// # }
504    /// ```
505    pub fn clear_data(&mut self) {
506        self.interpreter.set_init_data(Value::new_object());
507        self.prepared = false;
508    }
509
510    /// Add data document.
511    ///
512    /// The specified data document is merged into existing data document.
513    ///
514    /// ```
515    /// # use regorus::*;
516    /// # fn main() -> anyhow::Result<()> {
517    /// let mut engine = Engine::new();
518    ///
519    /// // Only objects can be added.
520    /// assert!(engine.add_data(Value::from_json_str("[]")?).is_err());
521    ///
522    /// // Merge { "x" : 1, "y" : {} }
523    /// assert!(engine
524    ///     .add_data(Value::from_json_str(r#"{ "x" : 1, "y" : {}}"#)?)
525    ///     .is_ok());
526    ///
527    /// // Merge { "z" : 2 }
528    /// assert!(engine
529    ///     .add_data(Value::from_json_str(r#"{ "z" : 2 }"#)?)
530    ///     .is_ok());
531    ///
532    /// // Merge { "z" : 3 }. Conflict error.
533    /// assert!(engine
534    ///     .add_data(Value::from_json_str(r#"{ "z" : 3 }"#)?)
535    ///     .is_err());
536    ///
537    /// assert_eq!(
538    ///     engine.eval_query("data".to_string(), false)?.result[0].expressions[0].value,
539    ///     Value::from_json_str(r#"{ "x": 1, "y": {}, "z": 2}"#)?
540    /// );
541    /// # Ok(())
542    /// # }
543    /// ```
544    pub fn add_data(&mut self, data: Value) -> Result<()> {
545        if data.as_object().is_err() {
546            bail!("data must be object");
547        }
548        self.prepared = false;
549        self.interpreter.get_init_data_mut().merge(data)
550    }
551
552    /// Get the data document.
553    ///
554    /// The returned value is the data document that has been constructed using
555    /// one or more calls to [`Engine::pre`]. The values of policy rules are
556    /// not included in the returned document.
557    ///
558    ///
559    /// ```
560    /// # use regorus::*;
561    /// # fn main() -> anyhow::Result<()> {
562    /// let mut engine = Engine::new();
563    ///
564    /// // If not set, data document is empty.
565    /// assert_eq!(engine.get_data(), Value::new_object());
566    ///
567    /// // Merge { "x" : 1, "y" : {} }
568    /// assert!(engine
569    ///     .add_data(Value::from_json_str(r#"{ "x" : 1, "y" : {}}"#)?)
570    ///     .is_ok());
571    ///
572    /// // Merge { "z" : 2 }
573    /// assert!(engine
574    ///     .add_data(Value::from_json_str(r#"{ "z" : 2 }"#)?)
575    ///     .is_ok());
576    ///
577    /// let data = engine.get_data();
578    /// assert_eq!(data["x"], Value::from(1));
579    /// assert_eq!(data["y"], Value::new_object());
580    /// assert_eq!(data["z"], Value::from(2));
581    ///
582    /// # Ok(())
583    /// # }
584    /// ```
585    pub fn get_data(&self) -> Value {
586        self.interpreter.get_init_data().clone()
587    }
588
589    pub fn add_data_json(&mut self, data_json: &str) -> Result<()> {
590        self.add_data(Value::from_json_str(data_json)?)
591    }
592
593    /// Set whether builtins should raise errors strictly or not.
594    ///
595    /// Regorus differs from OPA in that by default builtins will
596    /// raise errors instead of returning Undefined.
597    ///
598    /// ----
599    /// **_NOTE:_** Currently not all builtins honor this flag and will always strictly raise errors.
600    /// ----
601    pub fn set_strict_builtin_errors(&mut self, b: bool) {
602        self.interpreter.set_strict_builtin_errors(b);
603    }
604
605    #[doc(hidden)]
606    pub fn get_modules(&mut self) -> &Vec<Ref<Module>> {
607        &self.modules
608    }
609
610    /// Compiles a target-aware policy from the current engine state.
611    ///
612    /// This method creates a compiled policy that can work with Azure Policy targets,
613    /// enabling resource type inference and target-specific evaluation. The compiled
614    /// policy will automatically detect and handle `__target__` declarations in the
615    /// loaded modules.
616    ///
617    /// The engine must have been prepared with:
618    /// - Policy modules added via [`Engine::add_policy`]
619    /// - Data added via [`Engine::add_data`] (optional)
620    ///
621    /// # Returns
622    ///
623    /// Returns a [`CompiledPolicy`] that can be used for efficient policy evaluation
624    /// with target support, including resource type inference capabilities.
625    ///
626    /// # Examples
627    ///
628    /// ## Basic Target-Aware Compilation
629    ///
630    /// ```no_run
631    /// use regorus::*;
632    ///
633    /// # fn main() -> anyhow::Result<()> {
634    /// let mut engine = Engine::new();
635    /// engine.add_data(Value::from_json_str(
636    ///     r#"{"allowed_sizes": ["small", "medium"]}"#,
637    /// )?)?;
638    /// engine.add_policy(
639    ///     "policy.rego".to_string(),
640    ///     r#"
641    ///     package policy.test
642    ///     import rego.v1
643    ///     __target__ := "target.tests.sample_test_target"
644    ///     
645    ///     default allow := false
646    ///     allow if {
647    ///         input.type == "vm"
648    ///         input.size in data.allowed_sizes
649    ///     }
650    /// "#
651    ///     .to_string(),
652    /// )?;
653    ///
654    /// let compiled = engine.compile_for_target()?;
655    /// let result =
656    ///     compiled.eval_with_input(Value::from_json_str(r#"{"type": "vm", "size": "small"}"#)?)?;
657    /// # Ok(())
658    /// # }
659    /// ```
660    ///
661    /// ## Target Registration and Usage
662    ///
663    /// ```no_run
664    /// use regorus::{registry::targets, target::Target, *};
665    /// use std::sync::Arc;
666    ///
667    /// # fn main() -> anyhow::Result<()> {
668    /// // Register a target first
669    /// let target_json = r#"
670    /// {
671    ///   "name": "target.example.vm_policy",
672    ///   "description": "Simple VM validation target",
673    ///   "version": "1.0.0",
674    ///   "resource_schema_selector": "type",
675    ///   "resource_schemas": [
676    ///     {
677    ///       "type": "object",
678    ///       "properties": {
679    ///         "name": { "type": "string" },
680    ///         "type": { "const": "vm" },
681    ///         "size": { "enum": ["small", "medium", "large"] }
682    ///       },
683    ///       "required": ["name", "type", "size"]
684    ///     }
685    ///   ],
686    ///   "effects": {
687    ///     "allow": { "type": "boolean" },
688    ///     "deny": { "type": "boolean" }
689    ///   }
690    /// }
691    /// "#;
692    ///
693    /// let target = Target::from_json_str(target_json)?;
694    /// targets::register(Arc::new(target))?;
695    ///
696    /// // Use the target in a policy
697    /// let mut engine = Engine::new();
698    /// engine.add_data(Value::from_json_str(
699    ///     r#"{"allowed_locations": ["us-east"]}"#,
700    /// )?)?;
701    /// engine.add_policy(
702    ///     "vm_policy.rego".to_string(),
703    ///     r#"
704    ///     package vm.validation
705    ///     import rego.v1
706    ///     __target__ := "target.example.vm_policy"
707    ///     
708    ///     default allow := false
709    ///     allow if {
710    ///         input.type == "vm"
711    ///         input.size in ["small", "medium"]
712    ///     }
713    /// "#
714    ///     .to_string(),
715    /// )?;
716    ///
717    /// let compiled = engine.compile_for_target()?;
718    /// let result = compiled.eval_with_input(Value::from_json_str(
719    ///     r#"
720    /// {
721    ///   "name": "test-vm",
722    ///   "type": "vm",
723    ///   "size": "small"
724    /// }"#,
725    /// )?)?;
726    /// assert_eq!(result, Value::from(true));
727    /// # Ok(())
728    /// # }
729    /// ```
730    ///
731    /// # Notes
732    ///
733    /// - This method is only available when the `azure_policy` feature is enabled
734    /// - Automatically enables print gathering for debugging purposes
735    /// - Requires that at least one module contains a `__target__` declaration
736    /// - The target referenced must be registered in the target registry
737    ///
738    /// # See Also
739    ///
740    /// - [`Engine::compile_with_entrypoint`] for explicit rule-based compilation
741    /// - [`crate::compile_policy_for_target`] for a higher-level convenience function
742    #[cfg(feature = "azure_policy")]
743    #[cfg_attr(docsrs, doc(cfg(feature = "azure_policy")))]
744    pub fn compile_for_target(&mut self) -> Result<CompiledPolicy> {
745        self.prepare_for_eval(false, true)?;
746        self.apply_effective_execution_timer_config();
747        self.interpreter.clean_internal_evaluation_state();
748        self.interpreter.compile(None).map(CompiledPolicy::new)
749    }
750
751    /// Compiles a policy with a specific entry point rule.
752    ///
753    /// This method creates a compiled policy that evaluates a specific rule as the entry point.
754    /// Unlike [`Engine::compile_for_target`], this method requires you to explicitly specify which
755    /// rule should be evaluated and does not automatically handle target-specific features.
756    ///
757    /// The engine must have been prepared with:
758    /// - Policy modules added via [`Engine::add_policy`]
759    /// - Data added via [`Engine::add_data`] (optional)
760    ///
761    /// # Arguments
762    ///
763    /// * `rule` - The specific rule path to evaluate (e.g., "data.policy.allow")
764    ///
765    /// # Returns
766    ///
767    /// Returns a [`CompiledPolicy`] that can be used for efficient policy evaluation
768    /// focused on the specified entry point rule.
769    ///
770    /// # Examples
771    ///
772    /// ## Basic Usage
773    ///
774    /// ```no_run
775    /// use regorus::*;
776    /// use std::rc::Rc;
777    ///
778    /// # fn main() -> anyhow::Result<()> {
779    /// let mut engine = Engine::new();
780    /// engine.add_data(Value::from_json_str(
781    ///     r#"{"allowed_users": ["alice", "bob"]}"#,
782    /// )?)?;
783    /// engine.add_policy(
784    ///     "authz.rego".to_string(),
785    ///     r#"
786    ///     package authz
787    ///     import rego.v1
788    ///     
789    ///     default allow := false
790    ///     allow if {
791    ///         input.user in data.allowed_users
792    ///         input.action == "read"
793    ///     }
794    ///     
795    ///     deny if {
796    ///         input.user == "guest"
797    ///     }
798    /// "#
799    ///     .to_string(),
800    /// )?;
801    ///
802    /// let compiled = engine.compile_with_entrypoint(&"data.authz.allow".into())?;
803    /// let result = compiled.eval_with_input(Value::from_json_str(
804    ///     r#"{"user": "alice", "action": "read"}"#,
805    /// )?)?;
806    /// assert_eq!(result, Value::from(true));
807    /// # Ok(())
808    /// # }
809    /// ```
810    ///
811    /// ## Multi-Module Policy
812    ///
813    /// ```no_run
814    /// use regorus::*;
815    /// use std::rc::Rc;
816    ///
817    /// # fn main() -> anyhow::Result<()> {
818    /// let mut engine = Engine::new();
819    /// engine.add_data(Value::from_json_str(
820    ///     r#"{"departments": {"engineering": ["alice"], "hr": ["bob"]}}"#,
821    /// )?)?;
822    ///
823    /// engine.add_policy(
824    ///     "users.rego".to_string(),
825    ///     r#"
826    ///     package users
827    ///     import rego.v1
828    ///     
829    ///     user_department(user) := dept if {
830    ///         dept := [d | data.departments[d][_] == user][0]
831    ///     }
832    /// "#
833    ///     .to_string(),
834    /// )?;
835    ///
836    /// engine.add_policy(
837    ///     "permissions.rego".to_string(),
838    ///     r#"
839    ///     package permissions
840    ///     import rego.v1
841    ///     import data.users
842    ///     
843    ///     default allow := false
844    ///     allow if {
845    ///         users.user_department(input.user) == "engineering"
846    ///         input.resource.type == "code"
847    ///     }
848    ///     
849    ///     allow if {
850    ///         users.user_department(input.user) == "hr"
851    ///         input.resource.type == "personnel_data"
852    ///     }
853    /// "#
854    ///     .to_string(),
855    /// )?;
856    ///
857    /// let compiled = engine.compile_with_entrypoint(&"data.permissions.allow".into())?;
858    ///
859    /// // Test engineering access to code
860    /// let result = compiled.eval_with_input(Value::from_json_str(
861    ///     r#"
862    /// {
863    ///   "user": "alice",
864    ///   "resource": {"type": "code", "name": "main.rs"}
865    /// }"#,
866    /// )?)?;
867    /// assert_eq!(result, Value::from(true));
868    /// # Ok(())
869    /// # }
870    /// ```
871    ///
872    /// # Entry Point Rule Format
873    ///
874    /// The `rule` parameter should follow the Rego rule path format:
875    /// - `"data.package.rule"` - For rules in a specific package
876    /// - `"data.package.subpackage.rule"` - For nested packages
877    /// - `"allow"` - For rules in the default package (though this is not recommended)
878    ///
879    /// # Notes
880    ///
881    /// - Automatically enables print gathering for debugging purposes
882    /// - If you need target-aware compilation with automatic `__target__` handling,
883    ///   consider using [`Engine::compile_for_target`] instead (requires `azure_policy` feature)
884    ///
885    /// # See Also
886    ///
887    /// - [`Engine::compile_for_target`] for target-aware compilation
888    /// - [`crate::compile_policy_with_entrypoint`] for a higher-level convenience function
889    pub fn compile_with_entrypoint(&mut self, rule: &Rc<str>) -> Result<CompiledPolicy> {
890        self.prepare_for_eval(false, false)?;
891        self.apply_effective_execution_timer_config();
892        self.interpreter.clean_internal_evaluation_state();
893        self.interpreter
894            .compile(Some(rule.clone()))
895            .map(CompiledPolicy::new)
896    }
897
898    /// Evaluate specified rule(s).
899    ///
900    /// [`Engine::eval_rule`] is often faster than [`Engine::eval_query`] and should be preferred if
901    /// OPA style [`QueryResults`] are not needed.
902    ///
903    /// ```
904    /// # use regorus::*;
905    /// # fn main() -> anyhow::Result<()> {
906    /// let mut engine = Engine::new();
907    ///
908    /// // Add policy
909    /// engine.add_policy(
910    ///     "policy.rego".to_string(),
911    ///     r#"
912    ///   package example
913    ///   import rego.v1
914    ///
915    ///   x = [1, 2]
916    ///
917    ///   y := 5 if input.a > 2
918    ///   "#
919    ///     .to_string(),
920    /// )?;
921    ///
922    /// // Evaluate rule.
923    /// let v = engine.eval_rule("data.example.x".to_string())?;
924    /// assert_eq!(v, Value::from(vec![Value::from(1), Value::from(2)]));
925    ///
926    /// // y evaluates to undefined.
927    /// let v = engine.eval_rule("data.example.y".to_string())?;
928    /// assert_eq!(v, Value::Undefined);
929    ///
930    /// // Evaluating a non-existent rule is an error.
931    /// let r = engine.eval_rule("data.exaample.x".to_string());
932    /// assert!(r.is_err());
933    ///
934    /// // Path must be valid rule paths.
935    /// assert!(engine.eval_rule("data".to_string()).is_err());
936    /// assert!(engine.eval_rule("data.example".to_string()).is_err());
937    /// # Ok(())
938    /// # }
939    /// ```
940    pub fn eval_rule(&mut self, rule: String) -> Result<Value> {
941        self.prepare_for_eval(false, false)?;
942        self.apply_effective_execution_timer_config();
943        self.interpreter.clean_internal_evaluation_state();
944        self.interpreter.eval_rule_in_path(rule)
945    }
946
947    /// Evaluate a Rego query.
948    ///
949    /// ```
950    /// # use regorus::*;
951    /// # fn main() -> anyhow::Result<()> {
952    /// let mut engine = Engine::new();
953    ///
954    /// // Add policies
955    /// engine.set_rego_v0(true);
956    /// engine.add_policy_from_file("tests/aci/framework.rego")?;
957    /// engine.add_policy_from_file("tests/aci/api.rego")?;
958    /// engine.add_policy_from_file("tests/aci/policy.rego")?;
959    ///
960    /// // Add data document (if any).
961    /// // If multiple data documents can be added, they will be merged together.
962    /// engine.add_data(Value::from_json_file("tests/aci/data.json")?)?;
963    ///
964    /// // At this point the policies and data have been loaded.
965    /// // Either the same engine can be used to make multiple queries or the engine
966    /// // can be cloned to avoid having the reload the policies and data.
967    /// let _clone = engine.clone();
968    ///
969    /// // Evaluate a query.
970    /// // Load input and make query.
971    /// engine.set_input(Value::new_object());
972    /// let results = engine.eval_query("data.framework.mount_overlay.allowed".to_string(), false)?;
973    /// assert_eq!(results.result[0].expressions[0].value, Value::from(false));
974    ///
975    /// // Evaluate query with different inputs.
976    /// engine.set_input(Value::from_json_file("tests/aci/input.json")?);
977    /// let results = engine.eval_query("data.framework.mount_overlay.allowed".to_string(), false)?;
978    /// assert_eq!(results.result[0].expressions[0].value, Value::from(true));
979    /// # Ok(())
980    /// # }
981    /// ```
982    pub fn eval_query(&mut self, query: String, enable_tracing: bool) -> Result<QueryResults> {
983        self.prepare_for_eval(enable_tracing, false)?;
984        self.apply_effective_execution_timer_config();
985        self.interpreter.clean_internal_evaluation_state();
986
987        self.interpreter.create_rule_prefixes()?;
988        let (query_module, query_node, query_schedule) = self.make_query(query)?;
989        if query_node.span.text() == "data" {
990            self.eval_modules(enable_tracing)?;
991        }
992
993        self.interpreter
994            .eval_user_query(&query_module, &query_node, query_schedule, enable_tracing)
995    }
996
997    /// Evaluate a Rego query that produces a boolean value.
998    ///
999    ///
1000    /// This function should be preferred over [`Engine::eval_query`] if just a `true`/`false`
1001    /// value is desired instead of [`QueryResults`].
1002    ///
1003    /// ```
1004    /// # use regorus::*;
1005    /// # fn main() -> anyhow::Result<()> {
1006    /// # let mut engine = Engine::new();
1007    ///
1008    /// let enable_tracing = false;
1009    /// assert_eq!(
1010    ///     engine.eval_bool_query("1 > 2".to_string(), enable_tracing)?,
1011    ///     false
1012    /// );
1013    /// assert_eq!(
1014    ///     engine.eval_bool_query("1 < 2".to_string(), enable_tracing)?,
1015    ///     true
1016    /// );
1017    ///
1018    /// // Non boolean queries will raise an error.
1019    /// assert!(engine
1020    ///     .eval_bool_query("1+1".to_string(), enable_tracing)
1021    ///     .is_err());
1022    ///
1023    /// // Queries producing multiple values will raise an error.
1024    /// assert!(engine
1025    ///     .eval_bool_query("true; true".to_string(), enable_tracing)
1026    ///     .is_err());
1027    ///
1028    /// // Queries producing no values will raise an error.
1029    /// assert!(engine
1030    ///     .eval_bool_query("true; false; true".to_string(), enable_tracing)
1031    ///     .is_err());
1032    /// # Ok(())
1033    /// # }
1034    /// ```
1035    pub fn eval_bool_query(&mut self, query: String, enable_tracing: bool) -> Result<bool> {
1036        let results = self.eval_query(query, enable_tracing)?;
1037        let entries = results.result.as_slice();
1038        let entry = entries
1039            .first()
1040            .ok_or_else(|| anyhow!("query did not produce any values"))?;
1041        if entries.len() > 1 {
1042            bail!("query produced more than one value");
1043        }
1044
1045        let expressions = entry.expressions.as_slice();
1046        let expr = expressions
1047            .first()
1048            .ok_or_else(|| anyhow!("query result missing expression"))?;
1049        if expressions.len() > 1 {
1050            bail!("query produced more than one value");
1051        }
1052
1053        expr.value.as_bool().copied()
1054    }
1055
1056    /// Evaluate an `allow` query.
1057    ///
1058    /// This is a wrapper over [`Engine::eval_bool_query`] that returns true only if the
1059    /// boolean query succeed and produced a `true` value.
1060    ///
1061    /// ```
1062    /// # use regorus::*;
1063    /// # fn main() -> anyhow::Result<()> {
1064    /// # let mut engine = Engine::new();
1065    ///
1066    /// let enable_tracing = false;
1067    /// assert_eq!(
1068    ///     engine.eval_allow_query("1 > 2".to_string(), enable_tracing),
1069    ///     false
1070    /// );
1071    /// assert_eq!(
1072    ///     engine.eval_allow_query("1 < 2".to_string(), enable_tracing),
1073    ///     true
1074    /// );
1075    /// assert_eq!(
1076    ///     engine.eval_allow_query("1+1".to_string(), enable_tracing),
1077    ///     false
1078    /// );
1079    /// assert_eq!(
1080    ///     engine.eval_allow_query("true; true".to_string(), enable_tracing),
1081    ///     false
1082    /// );
1083    /// assert_eq!(
1084    ///     engine.eval_allow_query("true; false; true".to_string(), enable_tracing),
1085    ///     false
1086    /// );
1087    /// # Ok(())
1088    /// # }
1089    /// ```
1090    pub fn eval_allow_query(&mut self, query: String, enable_tracing: bool) -> bool {
1091        matches!(self.eval_bool_query(query, enable_tracing), Ok(true))
1092    }
1093
1094    /// Evaluate a `deny` query.
1095    ///
1096    /// This is a wrapper over [`Engine::eval_bool_query`] that returns false only if the
1097    /// boolean query succeed and produced a `false` value.
1098    /// ```
1099    /// # use regorus::*;
1100    /// # fn main() -> anyhow::Result<()> {
1101    /// # let mut engine = Engine::new();
1102    ///
1103    /// let enable_tracing = false;
1104    /// assert_eq!(
1105    ///     engine.eval_deny_query("1 > 2".to_string(), enable_tracing),
1106    ///     false
1107    /// );
1108    /// assert_eq!(
1109    ///     engine.eval_deny_query("1 < 2".to_string(), enable_tracing),
1110    ///     true
1111    /// );
1112    ///
1113    /// assert_eq!(
1114    ///     engine.eval_deny_query("1+1".to_string(), enable_tracing),
1115    ///     true
1116    /// );
1117    /// assert_eq!(
1118    ///     engine.eval_deny_query("true; true".to_string(), enable_tracing),
1119    ///     true
1120    /// );
1121    /// assert_eq!(
1122    ///     engine.eval_deny_query("true; false; true".to_string(), enable_tracing),
1123    ///     true
1124    /// );
1125    /// # Ok(())
1126    /// # }
1127    /// ```
1128    pub fn eval_deny_query(&mut self, query: String, enable_tracing: bool) -> bool {
1129        !matches!(self.eval_bool_query(query, enable_tracing), Ok(false))
1130    }
1131
1132    fn make_query(&mut self, query: String) -> Result<(NodeRef<Module>, NodeRef<Query>, Schedule)> {
1133        let mut query_module = {
1134            let source = Source::from_contents(
1135                "<query_module.rego>".to_owned(),
1136                "package __internal_query_module".to_owned(),
1137            )?;
1138            Parser::new(&source)?.parse()?
1139        };
1140
1141        // Parse the query.
1142        let query_source = Source::from_contents("<query.rego>".to_string(), query)?;
1143        let mut parser = self.make_parser(&query_source)?;
1144        let query_node = parser.parse_user_query()?;
1145        query_module.num_expressions = parser.num_expressions();
1146        query_module.num_queries = parser.num_queries();
1147        query_module.num_statements = parser.num_statements();
1148        let query_schedule = Analyzer::new().analyze_query_snippet(&self.modules, &query_node)?;
1149
1150        // Populate loop hoisting for the query snippet
1151        // Query snippets are treated as if they're in a module appended at the end (same as analyzer)
1152        // The loop hoisting table already has capacity for this (ensured in prepare_for_eval)
1153        let module_idx = u32::try_from(self.modules.len())
1154            .map_err(|_| anyhow!("module count exceeds u32::MAX"))?;
1155
1156        use crate::compiler::hoist::LoopHoister;
1157
1158        let query_schedule_rc = Rc::new(query_schedule.clone());
1159
1160        // Run loop hoisting for query snippet
1161        let mut hoister = LoopHoister::new_with_schedule(query_schedule_rc.clone());
1162        hoister.populate_query_snippet(
1163            module_idx,
1164            &query_node,
1165            query_module.num_statements,
1166            query_module.num_expressions,
1167        )?;
1168        let query_lookup = hoister.finalize();
1169
1170        #[cfg(debug_assertions)]
1171        {
1172            for stmt in &query_node.stmts {
1173                debug_assert!(
1174                    query_lookup
1175                        .get_statement_loops(module_idx, stmt.sidx)
1176                        .ok()
1177                        .and_then(|entry| entry)
1178                        .is_some(),
1179                    "missing hoisted loop entry for query statement index {}",
1180                    stmt.sidx
1181                );
1182            }
1183        }
1184
1185        // Get the existing table, merge in the query loops, and set it back
1186        let mut existing_table = self.interpreter.take_loop_hoisting_table();
1187        existing_table.truncate_modules(self.modules.len());
1188        #[cfg(debug_assertions)]
1189        {
1190            debug_assert!(
1191                existing_table.module_len() <= self.modules.len(),
1192                "loop hoisting table should not retain extra modules before merge"
1193            );
1194        }
1195        existing_table.merge_query_loops(query_lookup, self.modules.len());
1196        #[cfg(debug_assertions)]
1197        {
1198            for stmt in &query_node.stmts {
1199                debug_assert!(
1200                    existing_table
1201                        .get_statement_loops(module_idx, stmt.sidx)
1202                        .ok()
1203                        .and_then(|entry| entry)
1204                        .is_some(),
1205                    "missing hoisted loop entry after merge for module {} stmt {}",
1206                    module_idx,
1207                    stmt.sidx
1208                );
1209            }
1210        }
1211        self.interpreter.set_loop_hoisting_table(existing_table);
1212
1213        Ok((Ref::new(query_module), query_node, query_schedule))
1214    }
1215
1216    #[doc(hidden)]
1217    /// Evaluate the given query and all the rules in the supplied policies.
1218    ///
1219    /// This is mainly used for testing Regorus itself.
1220    pub fn eval_query_and_all_rules(
1221        &mut self,
1222        query: String,
1223        enable_tracing: bool,
1224    ) -> Result<QueryResults> {
1225        self.eval_modules(enable_tracing)?;
1226        // Restart the timer window for the user query after module evaluation.
1227        self.apply_effective_execution_timer_config();
1228
1229        let (query_module, query_node, query_schedule) = self.make_query(query)?;
1230        self.interpreter
1231            .eval_user_query(&query_module, &query_node, query_schedule, enable_tracing)
1232    }
1233
1234    #[doc(hidden)]
1235    fn prepare_for_eval(&mut self, enable_tracing: bool, for_target: bool) -> Result<()> {
1236        // Fail fast if the engine already exceeds the global memory limit before evaluation work.
1237        limits::enforce_memory_limit().map_err(|err| anyhow!(err))?;
1238
1239        self.interpreter.set_traces(enable_tracing);
1240
1241        // if the data/policies have changed or the interpreter has never been prepared
1242        if !self.prepared {
1243            // Analyze the modules and determine how statements must be scheduled.
1244            let analyzer = Analyzer::new();
1245            let schedule = Rc::new(analyzer.analyze(&self.modules)?);
1246
1247            self.interpreter.set_modules(self.modules.clone());
1248
1249            self.interpreter.clear_builtins_cache();
1250            // clean_internal_evaluation_state will set data to an efficient clont of use supplied init_data
1251            // Initialize the with-document with initial data values.
1252            // with-modifiers will be applied to this document.
1253            self.interpreter.init_with_document()?;
1254
1255            self.interpreter
1256                .set_functions(gather_functions(&self.modules)?);
1257            self.interpreter.gather_rules()?;
1258            self.interpreter.process_imports()?;
1259
1260            // Populate loop hoisting table for efficient evaluation
1261            // Reserve capacity for 1 extra module (for query modules)
1262            use crate::compiler::hoist::LoopHoister;
1263
1264            // Run loop hoisting pass first
1265            let hoister = LoopHoister::new_with_schedule(schedule.clone());
1266            let loop_lookup = hoister.populate_with_extra_capacity(&self.modules, 0)?;
1267
1268            self.interpreter.set_loop_hoisting_table(loop_lookup);
1269
1270            // Set schedule after hoisting completes
1271            self.interpreter.set_schedule(Some(schedule));
1272
1273            #[cfg(feature = "azure_policy")]
1274            if for_target {
1275                // Resolve and validate target specifications across all modules
1276                crate::interpreter::target::resolve::resolve_and_apply_target(
1277                    &mut self.interpreter,
1278                )?;
1279                // Infer resource types
1280                crate::interpreter::target::infer::infer_resource_type(&mut self.interpreter)?;
1281            }
1282
1283            if !for_target {
1284                // Check if any module specifies a target and warn if so
1285                #[cfg(feature = "azure_policy")]
1286                self.warn_if_targets_present();
1287            }
1288
1289            self.prepared = true;
1290        }
1291
1292        Ok(())
1293    }
1294
1295    #[doc(hidden)]
1296    pub fn eval_rule_in_module(
1297        &mut self,
1298        module: &Ref<Module>,
1299        rule: &Ref<Rule>,
1300        enable_tracing: bool,
1301    ) -> Result<Value> {
1302        self.prepare_for_eval(enable_tracing, false)?;
1303        self.apply_effective_execution_timer_config();
1304        self.interpreter.clean_internal_evaluation_state();
1305
1306        self.interpreter.eval_rule(module, rule)?;
1307
1308        Ok(self.interpreter.get_data_mut().clone())
1309    }
1310
1311    #[doc(hidden)]
1312    pub fn eval_modules(&mut self, enable_tracing: bool) -> Result<Value> {
1313        self.prepare_for_eval(enable_tracing, false)?;
1314        self.apply_effective_execution_timer_config();
1315        self.interpreter.clean_internal_evaluation_state();
1316
1317        // Ensure that empty modules are created.
1318        for m in self.modules.iter().filter(|m| m.policy.is_empty()) {
1319            let path = Parser::get_path_ref_components(&m.package.refr)?;
1320            let path: Vec<&str> = path.iter().map(|s| s.text()).collect();
1321            let vref =
1322                Interpreter::make_or_get_value_mut(self.interpreter.get_data_mut(), &path[..])?;
1323            if *vref == Value::Undefined {
1324                *vref = Value::new_object();
1325            }
1326        }
1327
1328        self.interpreter.check_default_rules()?;
1329        for module in self.modules.clone().iter() {
1330            for rule in &module.policy {
1331                self.interpreter.eval_rule(module, rule)?;
1332            }
1333        }
1334        // Defer the evaluation of the default rules to here
1335        for module in self.modules.clone().iter() {
1336            let prev_module = self.interpreter.set_current_module(Some(module.clone()))?;
1337            for rule in &module.policy {
1338                self.interpreter.eval_default_rule(rule)?;
1339            }
1340            self.interpreter.set_current_module(prev_module)?;
1341        }
1342
1343        // Ensure that all modules are created.
1344        for m in self.modules.iter() {
1345            let path = Parser::get_path_ref_components(&m.package.refr)?;
1346            let path: Vec<&str> = path.iter().map(|s| s.text()).collect();
1347            let vref =
1348                Interpreter::make_or_get_value_mut(self.interpreter.get_data_mut(), &path[..])?;
1349            if *vref == Value::Undefined {
1350                *vref = Value::new_object();
1351            }
1352        }
1353        self.interpreter.create_rule_prefixes()?;
1354        Ok(self.interpreter.get_data_mut().clone())
1355    }
1356
1357    /// Add a custom builtin (extension).
1358    ///
1359    /// * `path`: The fully qualified path of the builtin.
1360    /// * `nargs`: The number of arguments the builtin takes.
1361    /// * `extension`: The [`Extension`] instance.
1362    ///
1363    /// ```rust
1364    /// # use regorus::*;
1365    /// # use anyhow::{bail, Result};
1366    /// # fn main() -> Result<()> {
1367    /// let mut engine = Engine::new();
1368    ///
1369    /// // Policy uses `do_magic` custom builtin.
1370    /// engine.add_policy(
1371    ///     "test.rego".to_string(),
1372    ///     r#"package test
1373    ///       x = do_magic(1)
1374    ///    "#
1375    ///     .to_string(),
1376    /// )?;
1377    ///
1378    /// // Evaluating fails since `do_magic` is not defined.
1379    /// assert!(engine.eval_query("data.test.x".to_string(), false).is_err());
1380    ///
1381    /// // Add extension to implement `do_magic`. The extension can be stateful.
1382    /// let mut magic = 8;
1383    /// engine.add_extension(
1384    ///     "do_magic".to_string(),
1385    ///     1,
1386    ///     Box::new(move |mut params: Vec<Value>| {
1387    ///         // params is mut and therefore individual values can be removed from it and modified.
1388    ///         // The number of parameters (1) has already been validated.
1389    ///
1390    ///         match &params[0].as_i64() {
1391    ///             Ok(i) => {
1392    ///                 // Compute value
1393    ///                 let v = *i + magic;
1394    ///                 // Update extension state.
1395    ///                 magic += 1;
1396    ///                 Ok(Value::from(v))
1397    ///             }
1398    ///             // Extensions can raise errors. Regorus will add location information to
1399    ///             // the error.
1400    ///             _ => bail!("do_magic expects i64 value"),
1401    ///         }
1402    ///     }),
1403    /// )?;
1404    ///
1405    /// // Evaluation will now succeed.
1406    /// let r = engine.eval_query("data.test.x".to_string(), false)?;
1407    /// assert_eq!(r.result[0].expressions[0].value.as_i64()?, 9);
1408    ///
1409    /// // Cloning the engine will also clone the extension.
1410    /// let mut engine1 = engine.clone();
1411    ///
1412    /// // Evaluating again will return a different value since the extension is stateful.
1413    /// let r = engine.eval_query("data.test.x".to_string(), false)?;
1414    /// assert_eq!(r.result[0].expressions[0].value.as_i64()?, 10);
1415    ///
1416    /// // The second engine has a clone of the extension.
1417    /// let r = engine1.eval_query("data.test.x".to_string(), false)?;
1418    /// assert_eq!(r.result[0].expressions[0].value.as_i64()?, 10);
1419    ///
1420    /// // Once added, the extension cannot be replaced or removed.
1421    /// assert!(engine
1422    ///     .add_extension(
1423    ///         "do_magic".to_string(),
1424    ///         1,
1425    ///         Box::new(|_: Vec<Value>| { Ok(Value::Undefined) })
1426    ///     )
1427    ///     .is_err());
1428    ///
1429    /// // Extensions don't support out-parameter syntax.
1430    /// engine.add_policy(
1431    ///     "policy.rego".to_string(),
1432    ///     r#"package invalid
1433    ///      x = y if {
1434    ///       # y = do_magic(2)
1435    ///       do_magic(2, y)  # y is supplied as an out parameter.
1436    ///     }
1437    ///    "#
1438    ///     .to_string(),
1439    /// )?;
1440    ///
1441    /// // Evaluation fails since rule x calls an extension with out parameter.
1442    /// assert!(engine
1443    ///     .eval_query("data.invalid.x".to_string(), false)
1444    ///     .is_err());
1445    /// # Ok(())
1446    /// # }
1447    /// ```
1448    pub fn add_extension(
1449        &mut self,
1450        path: String,
1451        nargs: u8,
1452        extension: Box<dyn Extension>,
1453    ) -> Result<()> {
1454        self.interpreter.add_extension(path, nargs, extension)
1455    }
1456
1457    #[cfg(feature = "coverage")]
1458    #[cfg_attr(docsrs, doc(cfg(feature = "coverage")))]
1459    /// Get the coverage report.
1460    ///
1461    /// ```rust
1462    /// # use regorus::*;
1463    /// # use anyhow::{bail, Result};
1464    /// # fn main() -> Result<()> {
1465    /// let mut engine = Engine::new();
1466    ///
1467    /// engine.add_policy(
1468    ///     "policy.rego".to_string(),
1469    ///     r#"
1470    /// package test    # Line 2
1471    ///
1472    /// x = y if {         # Line 4
1473    ///   input.a > 2   # Line 5
1474    ///   y = 5         # Line 6
1475    /// }
1476    ///    "#
1477    ///     .to_string(),
1478    /// )?;
1479    ///
1480    /// // Enable coverage.
1481    /// engine.set_enable_coverage(true);
1482    ///
1483    /// engine.eval_query("data".to_string(), false)?;
1484    ///
1485    /// let report = engine.get_coverage_report()?;
1486    /// assert_eq!(report.files[0].path, "policy.rego");
1487    ///
1488    /// // Only line 5 is evaluated.
1489    /// assert_eq!(
1490    ///     report.files[0]
1491    ///         .covered
1492    ///         .iter()
1493    ///         .cloned()
1494    ///         .collect::<Vec<u32>>(),
1495    ///     vec![5]
1496    /// );
1497    ///
1498    /// // Line 4 and 6 are not evaluated.
1499    /// assert_eq!(
1500    ///     report.files[0]
1501    ///         .not_covered
1502    ///         .iter()
1503    ///         .cloned()
1504    ///         .collect::<Vec<u32>>(),
1505    ///     vec![4, 6]
1506    /// );
1507    /// # Ok(())
1508    /// # }
1509    /// ```
1510    ///
1511    /// See also [`crate::coverage::Report::to_colored_string`].
1512    pub fn get_coverage_report(&self) -> Result<crate::coverage::Report> {
1513        self.interpreter.get_coverage_report()
1514    }
1515
1516    #[cfg(feature = "coverage")]
1517    #[cfg_attr(docsrs, doc(cfg(feature = "coverage")))]
1518    /// Enable/disable policy coverage.
1519    ///
1520    /// If `enable` is different from the current value, then any existing coverage
1521    /// information will be cleared.
1522    pub fn set_enable_coverage(&mut self, enable: bool) {
1523        self.interpreter.set_enable_coverage(enable);
1524    }
1525
1526    #[cfg(feature = "coverage")]
1527    #[cfg_attr(docsrs, doc(cfg(feature = "coverage")))]
1528    /// Clear the gathered policy coverage data.
1529    pub fn clear_coverage_data(&mut self) {
1530        self.interpreter.clear_coverage_data();
1531    }
1532
1533    /// Gather output from print statements instead of emiting to stderr.
1534    ///
1535    /// See [`Engine::take_prints`].
1536    pub fn set_gather_prints(&mut self, b: bool) {
1537        self.interpreter.set_gather_prints(b);
1538    }
1539
1540    /// Take the gathered output of print statements.
1541    ///
1542    /// ```rust
1543    /// # use regorus::*;
1544    /// # use anyhow::{bail, Result};
1545    /// # fn main() -> Result<()> {
1546    /// let mut engine = Engine::new();
1547    ///
1548    /// // Print to stderr.
1549    /// engine.eval_query("print(\"Hello\")".to_string(), false)?;
1550    ///
1551    /// // Configure gathering print statements.
1552    /// engine.set_gather_prints(true);
1553    ///
1554    /// // Execute query.
1555    /// engine.eval_query("print(\"Hello\")".to_string(), false)?;
1556    ///
1557    /// // Take and clear prints.
1558    /// let prints = engine.take_prints()?;
1559    /// assert_eq!(prints.len(), 1);
1560    /// assert!(prints[0].contains("Hello"));
1561    ///
1562    /// for p in prints {
1563    ///     println!("{p}");
1564    /// }
1565    /// # Ok(())
1566    /// # }
1567    /// ```
1568    pub fn take_prints(&mut self) -> Result<Vec<String>> {
1569        self.interpreter.take_prints()
1570    }
1571
1572    /// Get the policies and corresponding AST.
1573    ///
1574    ///
1575    /// ```rust
1576    /// # use regorus::*;
1577    /// # use anyhow::{bail, Result};
1578    /// # fn main() -> Result<()> {
1579    /// # let mut engine = Engine::new();
1580    /// engine.add_policy("test.rego".to_string(), "package test\n x := 1".to_string())?;
1581    ///
1582    /// let ast = engine.get_ast_as_json()?;
1583    /// let value = Value::from_json_str(&ast)?;
1584    ///
1585    /// assert_eq!(
1586    ///     value[0]["ast"]["package"]["refr"]["Var"][1]
1587    ///         .as_string()?
1588    ///         .as_ref(),
1589    ///     "test"
1590    /// );
1591    /// # Ok(())
1592    /// # }
1593    /// ```
1594    #[cfg(feature = "ast")]
1595    #[cfg_attr(docsrs, doc(cfg(feature = "ast")))]
1596    pub fn get_ast_as_json(&self) -> Result<String> {
1597        #[derive(Serialize)]
1598        struct Policy<'a> {
1599            source: &'a Source,
1600            version: u32,
1601            ast: &'a Module,
1602        }
1603        let mut ast = vec![];
1604        for m in self.modules.iter() {
1605            ast.push(Policy {
1606                source: &m.package.span.source,
1607                version: 1,
1608                ast: m,
1609            });
1610        }
1611
1612        serde_json::to_string_pretty(&ast).map_err(anyhow::Error::msg)
1613    }
1614
1615    /// Get the package names of each policy added to the engine.
1616    ///
1617    ///
1618    /// ```rust
1619    /// # use regorus::*;
1620    /// # use anyhow::{bail, Result};
1621    /// # fn main() -> Result<()> {
1622    /// # let mut engine = Engine::new();
1623    /// engine.add_policy("test.rego".to_string(), "package test\n x := 1".to_string())?;
1624    /// engine.add_policy(
1625    ///     "test2.rego".to_string(),
1626    ///     "package test.multi.segment\n x := 1".to_string(),
1627    /// )?;
1628    ///
1629    /// let package_names = engine.get_policy_package_names()?;
1630    ///
1631    /// assert_eq!("test", package_names[0].package_name);
1632    /// assert_eq!("test.multi.segment", package_names[1].package_name);
1633    /// # Ok(())
1634    /// # }
1635    /// ```
1636    #[cfg(feature = "azure_policy")]
1637    #[cfg_attr(docsrs, doc(cfg(feature = "azure_policy")))]
1638    pub fn get_policy_package_names(&self) -> Result<Vec<PolicyPackageNameDefinition>> {
1639        let mut package_names = vec![];
1640        for m in self.modules.iter() {
1641            let package_name = Interpreter::get_path_string(&m.package.refr, None)?;
1642            package_names.push(PolicyPackageNameDefinition {
1643                source_file: m.package.span.source.file().to_string(),
1644                package_name,
1645            });
1646        }
1647
1648        Ok(package_names)
1649    }
1650
1651    /// Get the parameters defined in each policy.
1652    ///
1653    ///
1654    /// ```rust
1655    /// # use regorus::*;
1656    /// # use anyhow::{bail, Result};
1657    /// # fn main() -> Result<()> {
1658    /// # let mut engine = Engine::new();
1659    /// engine.add_policy(
1660    ///     "test.rego".to_string(),
1661    ///     "package test default parameters.a = 5 parameters.b = 10\n x := 1".to_string(),
1662    /// )?;
1663    ///
1664    /// let parameters = engine.get_policy_parameters()?;
1665    ///
1666    /// assert_eq!("a", parameters[0].parameters[0].name);
1667    /// assert_eq!("b", parameters[0].modifiers[0].name);
1668    ///
1669    /// # Ok(())
1670    /// # }
1671    /// ```
1672    #[cfg(feature = "azure_policy")]
1673    #[cfg_attr(docsrs, doc(cfg(feature = "azure_policy")))]
1674    pub fn get_policy_parameters(&self) -> Result<Vec<PolicyParameters>> {
1675        let mut policy_parameter_definitions = vec![];
1676        for m in self.modules.iter() {
1677            let mut parameters = vec![];
1678            let mut modifiers = vec![];
1679
1680            for rule in &m.policy {
1681                match *rule.as_ref() {
1682                    // Extract parameter definitions from the policy rule
1683                    // e.g. default parameters.a = 5
1684                    Rule::Default { ref refr, .. } => {
1685                        let path = Parser::get_path_ref_components(refr)?;
1686                        let paths: Vec<&str> = path.iter().map(|s| s.text()).collect();
1687
1688                        if paths.len() == 2 && paths.first().is_some_and(|p| *p == "parameters") {
1689                            if let Some(name) = paths.get(1) {
1690                                // Todo: Fetch fields other than name from rego metadoc for the parameter
1691                                parameters.push(PolicyParameter {
1692                                    name: (*name).to_string(),
1693                                    modifiable: false,
1694                                    required: false,
1695                                });
1696                            }
1697                        }
1698                    }
1699                    // Extract modifiers to the parameters from the policy rule
1700                    // e.g. parameters.a = 5
1701                    Rule::Spec { ref head, .. } => {
1702                        match *head {
1703                            RuleHead::Compr { ref refr, .. } => {
1704                                let path = Parser::get_path_ref_components(refr)?;
1705                                let paths: Vec<&str> = path.iter().map(|s| s.text()).collect();
1706
1707                                if paths.len() == 2
1708                                    && paths.first().is_some_and(|p| *p == "parameters")
1709                                {
1710                                    if let Some(name) = paths.get(1) {
1711                                        // Todo: Fetch fields other than name from rego metadoc for the parameter
1712                                        modifiers.push(PolicyModifier {
1713                                            name: (*name).to_string(),
1714                                        });
1715                                    }
1716                                }
1717                            }
1718                            RuleHead::Func { .. } => {}
1719                            RuleHead::Set { .. } => {}
1720                        }
1721                    }
1722                }
1723            }
1724
1725            policy_parameter_definitions.push(PolicyParameters {
1726                source_file: m.package.span.source.file().to_string(),
1727                parameters,
1728                modifiers,
1729            });
1730        }
1731
1732        Ok(policy_parameter_definitions)
1733    }
1734
1735    /// Emit a warning if any modules contain target specifications but we're not using target-aware compilation.
1736    #[cfg(feature = "azure_policy")]
1737    fn warn_if_targets_present(&self) {
1738        let mut has_target = false;
1739        let mut target_files = Vec::new();
1740
1741        for module in self.modules.iter() {
1742            if module.target.is_some() {
1743                has_target = true;
1744                target_files.push(module.package.span.source.get_path());
1745            }
1746        }
1747
1748        if has_target {
1749            std::eprintln!(
1750                "Warning: Target specifications found in policy modules but not using target-aware compilation."
1751            );
1752            std::eprintln!("         The following files contain __target__ declarations:");
1753            for file in target_files {
1754                std::eprintln!("         - {}", file);
1755            }
1756            std::eprintln!("         Consider using compile_for_target() instead of compile_with_entrypoint() for target-aware evaluation.");
1757        }
1758    }
1759
1760    fn make_parser<'a>(&self, source: &'a Source) -> Result<Parser<'a>> {
1761        let mut parser = Parser::new(source)?;
1762        if self.rego_v1 {
1763            parser.enable_rego_v1()?;
1764        }
1765        Ok(parser)
1766    }
1767
1768    /// Create a new Engine from a compiled policy.
1769    #[doc(hidden)]
1770    pub(crate) fn new_from_compiled_policy(
1771        compiled_policy: Rc<crate::compiled_policy::CompiledPolicyData>,
1772    ) -> Self {
1773        let modules = compiled_policy.modules.clone();
1774        let mut engine = Self {
1775            modules,
1776            interpreter: Interpreter::new_from_compiled_policy(compiled_policy),
1777            rego_v1: true, // Value doesn't matter since this is used only for policy parsing
1778            prepared: true,
1779            execution_timer_config: None,
1780        };
1781        engine.apply_effective_execution_timer_config();
1782        engine
1783    }
1784}