use anyhow::{anyhow, bail, Result};
#[allow(dead_code)]
fn read_file(path: &String) -> Result<String> {
std::fs::read_to_string(path).map_err(|_| anyhow!("could not read {path}"))
}
#[allow(unused_variables)]
fn read_value_from_yaml_file(path: &String) -> Result<regorus::Value> {
#[cfg(feature = "yaml")]
return regorus::Value::from_yaml_file(path);
#[cfg(not(feature = "yaml"))]
bail!("regorus has not been built with yaml support");
}
fn read_value_from_json_file(path: &String) -> Result<regorus::Value> {
#[cfg(feature = "std")]
return regorus::Value::from_json_file(path);
#[cfg(not(feature = "std"))]
regorus::Value::from_json_str(&read_file(path)?)
}
fn add_policy_from_file(engine: &mut regorus::Engine, path: String) -> Result<String> {
#[cfg(feature = "std")]
return engine.add_policy_from_file(path);
#[cfg(not(feature = "std"))]
engine.add_policy(path.clone(), read_file(&path)?)
}
fn rego_eval(
bundles: &[String],
files: &[String],
input: Option<String>,
query: String,
enable_tracing: bool,
non_strict: bool,
#[cfg(feature = "coverage")] coverage: bool,
) -> Result<()> {
let mut engine = regorus::Engine::new();
engine.set_strict_builtin_errors(!non_strict);
#[cfg(feature = "coverage")]
engine.set_enable_coverage(coverage);
for dir in bundles.iter() {
let entries =
std::fs::read_dir(dir).or_else(|e| bail!("failed to read bundle {dir}.\n{e}"))?;
for entry in entries {
let entry = entry.or_else(|e| bail!("failed to unwrap entry. {e}"))?;
let path = entry.path();
match (path.is_file(), path.extension()) {
(true, Some(ext)) if ext == "rego" => {}
_ => continue,
}
let _package = add_policy_from_file(&mut engine, entry.path().display().to_string())?;
}
}
for file in files.iter() {
if file.ends_with(".rego") {
let _package = add_policy_from_file(&mut engine, file.clone())?;
} else {
let data = if file.ends_with(".json") {
read_value_from_json_file(file)?
} else if file.ends_with(".yaml") {
read_value_from_yaml_file(file)?
} else {
bail!("Unsupported data file `{file}`. Must be rego, json or yaml.");
};
engine.add_data(data)?;
}
}
if let Some(file) = input {
let input = if file.ends_with(".json") {
read_value_from_json_file(&file)?
} else if file.ends_with(".yaml") {
read_value_from_yaml_file(&file)?
} else {
bail!("Unsupported input file `{file}`. Must be json or yaml.")
};
engine.set_input(input);
}
let results = engine.eval_query(query, enable_tracing)?;
println!("{}", serde_json::to_string_pretty(&results)?);
#[cfg(feature = "coverage")]
if coverage {
let report = engine.get_coverage_report()?;
println!("{}", report.to_string_pretty()?);
}
Ok(())
}
fn rego_lex(file: String, verbose: bool) -> Result<()> {
use regorus::unstable::*;
#[cfg(feature = "std")]
let source = Source::from_file(file)?;
#[cfg(not(feature = "std"))]
let source = Source::from_contents(file.clone(), read_file(&file)?)?;
let mut lexer = Lexer::new(&source);
loop {
let token = lexer.next_token()?;
if token.0 == TokenKind::Eof {
break;
}
if verbose {
println!("{}", token.1.message("", ""));
}
println!("{token:?}");
}
Ok(())
}
fn rego_parse(file: String) -> Result<()> {
use regorus::unstable::*;
#[cfg(feature = "std")]
let source = Source::from_file(file)?;
#[cfg(not(feature = "std"))]
let source = Source::from_contents(file.clone(), read_file(&file)?)?;
let mut parser = Parser::new(&source)?;
let ast = parser.parse()?;
println!("{ast:#?}");
Ok(())
}
#[derive(clap::Subcommand)]
enum RegorusCommand {
Eval {
#[arg(long, short, value_name = "bundle")]
bundles: Vec<String>,
#[arg(long, short, value_name = "policy.rego|data.json|data.yaml")]
data: Vec<String>,
#[arg(long, short, value_name = "input.rego")]
input: Option<String>,
query: String,
#[arg(long, short)]
trace: bool,
#[arg(long, short)]
non_strict: bool,
#[cfg(feature = "coverage")]
#[arg(long, short)]
coverage: bool,
},
Lex {
file: String,
#[arg(long, short)]
verbose: bool,
},
Parse {
file: String,
},
}
#[derive(clap::Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
#[command(subcommand)]
command: RegorusCommand,
}
fn main() -> Result<()> {
use clap::Parser;
let cli = Cli::parse();
match cli.command {
RegorusCommand::Eval {
bundles,
data,
input,
query,
trace,
non_strict,
#[cfg(feature = "coverage")]
coverage,
} => rego_eval(
&bundles,
&data,
input,
query,
trace,
non_strict,
#[cfg(feature = "coverage")]
coverage,
),
RegorusCommand::Lex { file, verbose } => rego_lex(file, verbose),
RegorusCommand::Parse { file } => rego_parse(file),
}
}