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 ¶ms[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}