Skip to main content

newton_cli/commands/
regorus.rs

1//! Regorus CLI wrapper with Newton crypto extensions.
2//!
3//! This module wraps the regorus Rego policy engine CLI with Newton-specific
4//! extensions enabled, providing access to `newton.crypto.*` built-in functions.
5
6use anyhow::{anyhow, bail, Result};
7use clap::{Parser, Subcommand};
8
9/// Regorus Rego policy engine with Newton extensions.
10#[derive(Debug, Parser)]
11pub struct RegorusCommand {
12    #[command(subcommand)]
13    pub subcommand: RegorusSubcommand,
14}
15
16/// Regorus subcommands.
17#[derive(Debug, Subcommand)]
18pub enum RegorusSubcommand {
19    /// Evaluate a Rego query with Newton extensions.
20    Eval {
21        /// Directories containing Rego files.
22        #[arg(long, short, value_name = "bundle")]
23        bundles: Vec<String>,
24
25        /// Policy or data files. Rego, json or yaml.
26        #[arg(long, short, value_name = "policy.rego|data.json|data.yaml")]
27        data: Vec<String>,
28
29        /// Input file. json or yaml.
30        #[arg(long, short, value_name = "input.json")]
31        input: Option<String>,
32
33        /// Query. Rego query block.
34        query: String,
35
36        /// Enable tracing.
37        #[arg(long, short)]
38        trace: bool,
39
40        /// Perform non-strict evaluation (default behavior of OPA).
41        #[arg(long, short)]
42        non_strict: bool,
43
44        /// Display coverage information.
45        #[arg(long, short)]
46        coverage: bool,
47    },
48
49    /// Tokenize a Rego policy.
50    Lex {
51        /// Rego policy file.
52        file: String,
53
54        /// Verbose output.
55        #[arg(long, short)]
56        verbose: bool,
57    },
58
59    /// Parse a Rego policy.
60    Parse {
61        /// Rego policy file.
62        file: String,
63    },
64
65    /// Parse a Rego policy and dump AST.
66    Ast {
67        /// Rego policy file.
68        file: String,
69    },
70}
71
72impl RegorusCommand {
73    /// Execute the regorus command.
74    pub fn execute(self) -> Result<()> {
75        match self.subcommand {
76            RegorusSubcommand::Eval {
77                bundles,
78                data,
79                input,
80                query,
81                trace,
82                non_strict,
83                coverage,
84            } => rego_eval(&bundles, &data, input, query, trace, non_strict, coverage),
85            RegorusSubcommand::Lex { file, verbose } => rego_lex(file, verbose),
86            RegorusSubcommand::Parse { file } => rego_parse(file),
87            RegorusSubcommand::Ast { file } => rego_ast(file),
88        }
89    }
90}
91
92fn rego_eval(
93    bundles: &[String],
94    files: &[String],
95    input: Option<String>,
96    query: String,
97    enable_tracing: bool,
98    non_strict: bool,
99    coverage: bool,
100) -> Result<()> {
101    let mut engine = regorus::Engine::new();
102
103    // Register Newton crypto extensions
104    engine.with_newton_crypto_extensions()?;
105
106    engine.set_strict_builtin_errors(!non_strict);
107    engine.set_enable_coverage(coverage);
108
109    // Load files from given bundles
110    for dir in bundles.iter() {
111        let entries = std::fs::read_dir(dir).map_err(|e| anyhow!("failed to read bundle {dir}.\n{e}"))?;
112        for entry in entries {
113            let entry = entry.map_err(|e| anyhow!("failed to unwrap entry. {e}"))?;
114            let path = entry.path();
115
116            match (path.is_file(), path.extension()) {
117                (true, Some(ext)) if ext == "rego" => {}
118                _ => continue,
119            }
120
121            engine.add_policy_from_file(entry.path().display().to_string())?;
122        }
123    }
124
125    // Load given files
126    for file in files.iter() {
127        if file.ends_with(".rego") {
128            engine.add_policy_from_file(file.clone())?;
129        } else {
130            let data = if file.ends_with(".json") {
131                regorus::Value::from_json_file(file)?
132            } else if file.ends_with(".yaml") {
133                regorus::Value::from_yaml_file(file)?
134            } else {
135                bail!("Unsupported data file `{file}`. Must be rego, json or yaml.");
136            };
137            engine.add_data(data)?;
138        }
139    }
140
141    if let Some(file) = input {
142        let input = if file.ends_with(".json") {
143            regorus::Value::from_json_file(&file)?
144        } else if file.ends_with(".yaml") {
145            regorus::Value::from_yaml_file(&file)?
146        } else {
147            bail!("Unsupported input file `{file}`. Must be json or yaml.")
148        };
149        engine.set_input(input);
150    }
151
152    let results = engine.eval_query(query, enable_tracing)?;
153
154    println!("{}", serde_json::to_string_pretty(&results)?);
155
156    if coverage {
157        let report = engine.get_coverage_report()?;
158        println!("{}", report.to_string_pretty()?);
159    }
160
161    Ok(())
162}
163
164fn rego_lex(file: String, verbose: bool) -> Result<()> {
165    use regorus::unstable::*;
166
167    let source = Source::from_file(file)?;
168    let mut lexer = Lexer::new(&source);
169
170    loop {
171        let token = lexer.next_token()?;
172        if token.0 == TokenKind::Eof {
173            break;
174        }
175
176        if verbose {
177            println!("{}", token.1.message("", ""));
178        }
179
180        println!("{token:?}");
181    }
182    Ok(())
183}
184
185fn rego_parse(file: String) -> Result<()> {
186    use regorus::unstable::*;
187
188    let source = Source::from_file(file)?;
189    let mut parser = Parser::new(&source)?;
190    parser.enable_rego_v1()?;
191
192    let ast = parser.parse()?;
193    println!("{ast:#?}");
194
195    Ok(())
196}
197
198fn rego_ast(file: String) -> Result<()> {
199    let mut engine = regorus::Engine::new();
200    engine.add_policy_from_file(file)?;
201
202    let ast = engine.get_ast_as_json()?;
203    println!("{ast}");
204    Ok(())
205}