regorus/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#![cfg_attr(docsrs, feature(doc_cfg))]
5#![allow(unknown_lints)]
6#![allow(clippy::doc_lazy_continuation)]
7// Use README.md as crate documentation.
8#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
9// We'll default to building for no_std - use core, alloc instead of std.
10#![no_std]
11
12extern crate alloc;
13use serde::Serialize;
14
15// Import std crate if building with std support.
16// We don't import types or macros from std.
17// As a result, types and macros from std must be qualified via `std::`
18// making dependencies on std easier to spot.
19#[cfg(any(feature = "std", test))]
20extern crate std;
21
22mod ast;
23mod builtins;
24mod compile;
25mod compiled_policy;
26mod engine;
27mod indexchecker;
28mod interpreter;
29mod lexer;
30mod number;
31mod parser;
32mod policy_info;
33#[cfg(feature = "azure_policy")]
34pub mod registry;
35mod scheduler;
36#[cfg(feature = "azure_policy")]
37mod schema;
38#[cfg(feature = "azure_policy")]
39pub mod target;
40mod utils;
41mod value;
42
43#[cfg(feature = "azure_policy")]
44pub use {
45    compile::compile_policy_for_target,
46    schema::{error::ValidationError, validate::SchemaValidator, Schema},
47    target::Target,
48};
49
50pub use compile::{compile_policy_with_entrypoint, PolicyModule};
51pub use compiled_policy::CompiledPolicy;
52pub use engine::Engine;
53pub use lexer::Source;
54pub use policy_info::PolicyInfo;
55pub use value::Value;
56
57#[cfg(feature = "arc")]
58pub use alloc::sync::Arc as Rc;
59
60#[cfg(not(feature = "arc"))]
61pub use alloc::rc::Rc;
62
63#[cfg(feature = "std")]
64use std::collections::{hash_map::Entry as MapEntry, HashMap as Map, HashSet as Set};
65
66#[cfg(not(feature = "std"))]
67use alloc::collections::{btree_map::Entry as MapEntry, BTreeMap as Map, BTreeSet as Set};
68
69use alloc::{
70    borrow::ToOwned,
71    boxed::Box,
72    format,
73    string::{String, ToString},
74    vec,
75    vec::Vec,
76};
77
78use core::fmt;
79
80/// Location of an [`Expression`] in a Rego query.
81///
82/// ```
83/// # use regorus::Engine;
84/// # fn main() -> anyhow::Result<()> {
85/// // Create engine and evaluate "  \n  1 + 2".
86/// let results = Engine::new().eval_query("  \n  1 + 2".to_string(), false)?;
87///
88/// // Fetch the location for the expression.
89/// let loc = &results.result[0].expressions[0].location;
90///
91/// assert_eq!(loc.row, 2);
92/// assert_eq!(loc.col, 3);
93/// # Ok(())
94/// # }
95/// ````
96/// See also [`QueryResult`].
97#[derive(Debug, Clone, Serialize, Eq, PartialEq)]
98pub struct Location {
99    /// Line number. Starts at 1.
100    pub row: u32,
101    /// Column number. Starts at 1.
102    pub col: u32,
103}
104
105/// An expression in a Rego query.
106///
107/// ```
108/// # use regorus::*;
109/// # fn main() -> anyhow::Result<()> {
110/// // Create engine and evaluate "1 + 2".
111/// let results = Engine::new().eval_query("1 + 2".to_string(), false)?;
112///
113/// // Fetch the expression from results.
114/// let expr = &results.result[0].expressions[0];
115///
116/// assert_eq!(expr.value, Value::from(3u64));
117/// assert_eq!(expr.text.as_ref(), "1 + 2");
118/// # Ok(())
119/// # }
120/// ```
121/// See also [`QueryResult`].
122#[derive(Debug, Clone, Serialize, Eq, PartialEq)]
123pub struct Expression {
124    /// Computed value of the expression.
125    pub value: Value,
126
127    /// The Rego expression.
128    pub text: Rc<str>,
129
130    /// Location of the expression in the query string.
131    pub location: Location,
132}
133
134/// Result of evaluating a Rego query.
135///
136/// A query containing single expression.
137/// ```
138/// # use regorus::*;
139/// # fn main() -> anyhow::Result<()> {
140/// // Create engine and evaluate "1 + 2".
141/// let results = Engine::new().eval_query("1 + 2".to_string(), false)?;
142///
143/// // Fetch the first (sole) result.
144/// let result = &results.result[0];
145///
146/// assert_eq!(result.expressions[0].value, Value::from(3u64));
147/// assert_eq!(result.expressions[0].text.as_ref(), "1 + 2");
148/// # Ok(())
149/// # }
150/// ```
151///
152/// A query containing multiple expressions.
153/// ```
154/// # use regorus::*;
155/// # fn main() -> anyhow::Result<()> {
156/// // Create engine and evaluate "1 + 2; 3.5 * 4".
157/// let results = Engine::new().eval_query("1 + 2; 3.55 * 4".to_string(), false)?;
158///
159/// // Fetch the first (sole) result.
160/// let result = &results.result[0];
161///
162/// // First expression.
163/// assert_eq!(result.expressions[0].value, Value::from(3u64));
164/// assert_eq!(result.expressions[0].text.as_ref(), "1 + 2");
165///
166/// // Second expression.
167/// assert_eq!(result.expressions[1].value, Value::from(14.2));
168/// assert_eq!(result.expressions[1].text.as_ref(), "3.55 * 4");
169/// # Ok(())
170/// # }
171/// ```
172///
173/// Expressions that create bindings (i.e. associate names to values) evaluate to
174/// either true or false. The value of bindings are available in the `bindings` field.
175/// ```
176/// # use regorus::*;
177/// # fn main() -> anyhow::Result<()> {
178/// // Create engine and evaluate "x = 1; y = x > 0".
179/// let results = Engine::new().eval_query("x = 1; y = x > 0".to_string(), false)?;
180///
181/// // Fetch the first (sole) result.
182/// let result = &results.result[0];
183///
184/// // First expression is true.
185/// assert_eq!(result.expressions[0].value, Value::from(true));
186/// assert_eq!(result.expressions[0].text.as_ref(), "x = 1");
187///
188/// // Second expression is true.
189/// assert_eq!(result.expressions[1].value, Value::from(true));
190/// assert_eq!(result.expressions[1].text.as_ref(), "y = x > 0");
191///
192/// // bindings contains the value for each named expession.
193/// assert_eq!(result.bindings[&Value::from("x")], Value::from(1u64));
194/// assert_eq!(result.bindings[&Value::from("y")], Value::from(true));
195/// # Ok(())
196/// # }
197/// ```
198///
199/// If any expression evaluates to false, then no results are produced.
200/// ```
201/// # use regorus::*;
202/// # fn main() -> anyhow::Result<()> {
203/// // Create engine and evaluate "true; true; false".
204/// let results = Engine::new().eval_query("true; true; false".to_string(), false)?;
205///
206/// assert!(results.result.is_empty());
207/// # Ok(())
208/// # }
209/// ```
210#[derive(Debug, Clone, Serialize, Eq, PartialEq)]
211pub struct QueryResult {
212    /// Expressions in the query.
213    ///
214    /// Each statement in the query is treated as a separte expression.
215    ///
216    pub expressions: Vec<Expression>,
217
218    /// Bindings created in the query.
219    #[serde(skip_serializing_if = "Value::is_empty_object")]
220    pub bindings: Value,
221}
222
223impl Default for QueryResult {
224    fn default() -> Self {
225        Self {
226            bindings: Value::new_object(),
227            expressions: vec![],
228        }
229    }
230}
231
232/// Results of evaluating a Rego query.
233///
234/// Generates the same `json` representation as `opa eval`.
235///
236/// Queries typically produce a single result.
237/// ```
238/// # use regorus::*;
239/// # fn main() -> anyhow::Result<()> {
240/// // Create engine and evaluate "1 + 1".
241/// let results = Engine::new().eval_query("1 + 1".to_string(), false)?;
242///
243/// assert_eq!(results.result.len(), 1);
244/// assert_eq!(results.result[0].expressions[0].value, Value::from(2u64));
245/// assert_eq!(results.result[0].expressions[0].text.as_ref(), "1 + 1");
246/// # Ok(())
247/// # }
248/// ```
249///
250/// If a query contains only one expression, and even if the expression evaluates
251/// to false, the value will be returned.
252/// ```
253/// # use regorus::*;
254/// # fn main() -> anyhow::Result<()> {
255/// // Create engine and evaluate "1 > 2" which is false.
256/// let results = Engine::new().eval_query("1 > 2".to_string(), false)?;
257///
258/// assert_eq!(results.result.len(), 1);
259/// assert_eq!(results.result[0].expressions[0].value, Value::from(false));
260/// assert_eq!(results.result[0].expressions[0].text.as_ref(), "1 > 2");
261/// # Ok(())
262/// # }
263/// ```
264///
265/// In a query containing multiple expressions, if  any expression evaluates to false,
266/// then no results are produced.
267/// ```
268/// # use regorus::*;
269/// # fn main() -> anyhow::Result<()> {
270/// // Create engine and evaluate "true; true; false".
271/// let results = Engine::new().eval_query("true; true; false".to_string(), false)?;
272///
273/// assert!(results.result.is_empty());
274/// # Ok(())
275/// # }
276/// ```
277///
278/// Note that `=` is different from `==`. The former evaluates to undefined if the LHS and RHS
279/// are not equal. The latter evaluates to either true or false.
280/// ```
281/// # use regorus::*;
282/// # fn main() -> anyhow::Result<()> {
283/// // Create engine and evaluate "1 = 2" which is undefined and produces no resutl.
284/// let results = Engine::new().eval_query("1 = 2".to_string(), false)?;
285///
286/// assert_eq!(results.result.len(), 0);
287///
288/// // Create engine and evaluate "1 == 2" which evaluates to false.
289/// let results = Engine::new().eval_query("1 == 2".to_string(), false)?;
290///
291/// assert_eq!(results.result.len(), 1);
292/// assert_eq!(results.result[0].expressions[0].value, Value::from(false));
293/// assert_eq!(results.result[0].expressions[0].text.as_ref(), "1 == 2");
294/// # Ok(())
295/// # }
296/// ```
297///
298/// Queries containing loops produce multiple results.
299/// ```
300/// # use regorus::*;
301/// # fn main() -> anyhow::Result<()> {
302/// let results = Engine::new().eval_query("x = [1, 2, 3][_]".to_string(), false)?;
303///
304/// // Three results are produced, one of each value of x.
305/// assert_eq!(results.result.len(), 3);
306///
307/// // Assert expressions and bindings of results.
308/// assert_eq!(results.result[0].expressions[0].value, Value::Bool(true));
309/// assert_eq!(results.result[0].expressions[0].text.as_ref(), "x = [1, 2, 3][_]");
310/// assert_eq!(results.result[0].bindings[&Value::from("x")], Value::from(1u64));
311///
312/// assert_eq!(results.result[1].expressions[0].value, Value::Bool(true));
313/// assert_eq!(results.result[1].expressions[0].text.as_ref(), "x = [1, 2, 3][_]");
314/// assert_eq!(results.result[1].bindings[&Value::from("x")], Value::from(2u64));
315///
316/// assert_eq!(results.result[2].expressions[0].value, Value::Bool(true));
317/// assert_eq!(results.result[2].expressions[0].text.as_ref(), "x = [1, 2, 3][_]");
318/// assert_eq!(results.result[2].bindings[&Value::from("x")], Value::from(3u64));
319/// # Ok(())
320/// # }
321/// ```
322///
323/// Loop iterations that evaluate to false or undefined don't produce results.
324/// ```
325/// # use regorus::*;
326/// # fn main() -> anyhow::Result<()> {
327/// let results = Engine::new().eval_query("x = [1, 2, 3][_]; x >= 2".to_string(), false)?;
328///
329/// // Two results are produced, one for x = 2 and another for x = 3.
330/// assert_eq!(results.result.len(), 2);
331///
332/// // Assert expressions and bindings of results.
333/// assert_eq!(results.result[0].expressions[0].value, Value::Bool(true));
334/// assert_eq!(results.result[0].expressions[0].text.as_ref(), "x = [1, 2, 3][_]");
335/// assert_eq!(results.result[0].expressions[0].value, Value::Bool(true));
336/// assert_eq!(results.result[0].expressions[1].text.as_ref(), "x >= 2");
337/// assert_eq!(results.result[0].bindings[&Value::from("x")], Value::from(2u64));
338///
339/// assert_eq!(results.result[1].expressions[0].value, Value::Bool(true));
340/// assert_eq!(results.result[1].expressions[0].text.as_ref(), "x = [1, 2, 3][_]");
341/// assert_eq!(results.result[1].expressions[0].value, Value::Bool(true));
342/// assert_eq!(results.result[1].expressions[1].text.as_ref(), "x >= 2");
343/// assert_eq!(results.result[1].bindings[&Value::from("x")], Value::from(3u64));
344/// # Ok(())
345/// # }
346/// ```
347///
348/// See [QueryResult] for examples of different kinds of results.
349#[derive(Debug, Clone, Default, Serialize, Eq, PartialEq)]
350pub struct QueryResults {
351    /// Collection of results of evaluting a query.
352    #[serde(skip_serializing_if = "Vec::is_empty")]
353    pub result: Vec<QueryResult>,
354}
355
356/// A user defined builtin function implementation.
357///
358/// It is not necessary to implement this trait directly.
359pub trait Extension: FnMut(Vec<Value>) -> anyhow::Result<Value> + Send + Sync {
360    /// Fn, FnMut etc are not sized and cannot be cloned in their boxed form.
361    /// clone_box exists to overcome that.
362    fn clone_box<'a>(&self) -> Box<dyn 'a + Extension>
363    where
364        Self: 'a;
365}
366
367/// Automatically make matching closures a valid [`Extension`].
368impl<F> Extension for F
369where
370    F: FnMut(Vec<Value>) -> anyhow::Result<Value> + Clone + Send + Sync,
371{
372    fn clone_box<'a>(&self) -> Box<dyn 'a + Extension>
373    where
374        Self: 'a,
375    {
376        Box::new(self.clone())
377    }
378}
379
380/// Implement clone for a boxed extension using [`Extension::clone_box`].
381impl Clone for Box<dyn '_ + Extension> {
382    fn clone(&self) -> Self {
383        (**self).clone_box()
384    }
385}
386
387impl fmt::Debug for dyn Extension {
388    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> core::result::Result<(), fmt::Error> {
389        f.write_fmt(format_args!("<extension>"))
390    }
391}
392
393#[cfg(feature = "coverage")]
394#[cfg_attr(docsrs, doc(cfg(feature = "coverage")))]
395pub mod coverage {
396    use crate::*;
397
398    #[derive(Default, serde::Serialize, serde::Deserialize)]
399    /// Coverage information about a rego policy file.
400    pub struct File {
401        /// Path of the policy file.
402        pub path: String,
403
404        /// The rego policy.
405        pub code: String,
406
407        /// Lines that were evaluated.
408        pub covered: alloc::collections::BTreeSet<u32>,
409
410        /// Lines that were not evaluated.
411        pub not_covered: alloc::collections::BTreeSet<u32>,
412    }
413
414    #[derive(Default, serde::Serialize, serde::Deserialize)]
415    /// Policy coverage report.
416    pub struct Report {
417        /// Coverage information for files.
418        pub files: Vec<File>,
419    }
420
421    impl Report {
422        /// Produce an ANSI color encoded version of the report.
423        ///
424        /// Covered lines are green.
425        /// Lines that are not covered are red.
426        ///
427        /// <img src="https://github.com/microsoft/regorus/blob/main/docs/coverage.png?raw=true">
428        pub fn to_string_pretty(&self) -> anyhow::Result<String> {
429            let mut s = String::default();
430            s.push_str("COVERAGE REPORT:\n");
431            for file in self.files.iter() {
432                if file.not_covered.is_empty() {
433                    s.push_str(&format!("{} has full coverage\n", file.path));
434                    continue;
435                }
436
437                s.push_str(&format!("{}:\n", file.path));
438                for (line, code) in file.code.split('\n').enumerate() {
439                    let line = line as u32 + 1;
440                    if file.not_covered.contains(&line) {
441                        s.push_str(&format!("\x1b[31m {line:4}  {code}\x1b[0m\n"));
442                    } else if file.covered.contains(&line) {
443                        s.push_str(&format!("\x1b[32m {line:4}  {code}\x1b[0m\n"));
444                    } else {
445                        s.push_str(&format!(" {line:4}  {code}\n"));
446                    }
447                }
448            }
449
450            s.push('\n');
451            Ok(s)
452        }
453    }
454}
455
456/// Items in `unstable` are likely to change.
457#[doc(hidden)]
458pub mod unstable {
459    pub use crate::ast::*;
460    pub use crate::lexer::*;
461    pub use crate::parser::*;
462}
463
464#[cfg(test)]
465mod tests;