mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
Compare commits
11 Commits
regorus-v0
...
regorus-v0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b80ef2d015 | ||
|
|
7e3fc08a14 | ||
|
|
48982222c5 | ||
|
|
90757210bc | ||
|
|
7bc9a50a52 | ||
|
|
08f3007b5c | ||
|
|
863601c2d5 | ||
|
|
976c04be8a | ||
|
|
fbfed6b49c | ||
|
|
595f9d34d5 | ||
|
|
a8c0588426 |
20
.github/workflows/publish-java.yml
vendored
20
.github/workflows/publish-java.yml
vendored
@@ -15,9 +15,13 @@ jobs:
|
||||
- target: x86_64-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
extension: so
|
||||
build_cmd: zigbuild
|
||||
glibc: "2.17"
|
||||
- target: aarch64-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
extension: so
|
||||
build_cmd: zigbuild
|
||||
glibc: "2.17"
|
||||
- target: x86_64-apple-darwin
|
||||
os: macos-latest
|
||||
extension: dylib
|
||||
@@ -38,15 +42,13 @@ jobs:
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
- if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' }}
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
|
||||
# Setup for cargo
|
||||
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV
|
||||
|
||||
- run: cargo build --release --target ${{ matrix.target }} --manifest-path ./bindings/java/Cargo.toml
|
||||
- if: ${{ matrix.build_cmd == 'zigbuild' }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
- if: ${{ matrix.build_cmd == 'zigbuild' }}
|
||||
run: pip install cargo-zigbuild
|
||||
- run: cargo ${{ matrix.build_cmd || 'build' }} --release --target ${{ matrix.target }}${{ matrix.glibc && format('.{0}', matrix.glibc) || '' }} --manifest-path ./bindings/java/Cargo.toml
|
||||
- run: mkdir -p native/${{ matrix.target }}
|
||||
- run: mv target/${{ matrix.target }}/release/*.${{ matrix.extension }} ./native/${{ matrix.target }}/
|
||||
- uses: actions/upload-artifact@v4
|
||||
|
||||
14
CHANGELOG.md
14
CHANGELOG.md
@@ -6,6 +6,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.1.2](https://github.com/microsoft/regorus/compare/regorus-v0.1.1...regorus-v0.1.2) - 2024-03-22
|
||||
|
||||
### Other
|
||||
- Handle non simple refs in chained expressions ([#182](https://github.com/microsoft/regorus/pull/182))
|
||||
- Ability to gather print statements ([#179](https://github.com/microsoft/regorus/pull/179))
|
||||
- Top-down evaluation ([#177](https://github.com/microsoft/regorus/pull/177))
|
||||
- Make unary `-` operator OPA compatible. ([#175](https://github.com/microsoft/regorus/pull/175))
|
||||
- Don't use deprecated chrono `Duration` methods ([#173](https://github.com/microsoft/regorus/pull/173))
|
||||
- Propagate Undefined in object expressions ([#171](https://github.com/microsoft/regorus/pull/171))
|
||||
- Bump to OPA v0.62.0 ([#169](https://github.com/microsoft/regorus/pull/169))
|
||||
- Fix regression ([#164](https://github.com/microsoft/regorus/pull/164))
|
||||
- Separately keep track of whether rules have been evaluated or not ([#163](https://github.com/microsoft/regorus/pull/163))
|
||||
- Link Linux libraries against glibc 2.17 using `cargo-zigbuild` ([#158](https://github.com/microsoft/regorus/pull/158))
|
||||
|
||||
## [0.1.1](https://github.com/microsoft/regorus/compare/regorus-v0.1.0...regorus-v0.1.1) - 2024-02-23
|
||||
|
||||
### Other
|
||||
|
||||
@@ -10,7 +10,7 @@ members = [
|
||||
[package]
|
||||
name = "regorus"
|
||||
description = "A fast, lightweight Rego (OPA policy language) interpreter"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
edition = "2021"
|
||||
license-file = "LICENSE"
|
||||
repository = "https://github.com/microsoft/regorus"
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
Regorus is also
|
||||
- *cross-platform* - Written in platform-agnostic Rust.
|
||||
- *current* - We strive to keep Regorus up to date with latest OPA release. Regorus supports `import rego.v1`.
|
||||
- *compliant* - Regorus is mostly compliant with the latest [OPA release v0.61.0](https://github.com/open-policy-agent/opa/releases/tag/v0.61.0). See [OPA Conformance](#opa-conformance) for details. Note that while we behaviorally produce the same results, we don't yet support all the builtins.
|
||||
- *compliant* - Regorus is mostly compliant with the latest [OPA release v0.62.0](https://github.com/open-policy-agent/opa/releases/tag/v0.62.0). See [OPA Conformance](#opa-conformance) for details. Note that while we behaviorally produce the same results, we don't yet support all the builtins.
|
||||
- *extensible* - Extend the Rego language by implementing custom stateful builtins in Rust.
|
||||
See [add_extension](https://github.com/microsoft/regorus/blob/fc68bf9c8bea36427dae9401a7d1f6ada771f7ab/src/engine.rs#L352).
|
||||
Support for extensibility using other languages coming soon.
|
||||
|
||||
@@ -72,6 +72,7 @@ fn rego_eval(
|
||||
|
||||
// Evaluate query.
|
||||
let results = engine.eval_query(query, enable_tracing)?;
|
||||
|
||||
println!("{}", serde_json::to_string_pretty(&results)?);
|
||||
|
||||
#[cfg(feature = "coverage")]
|
||||
@@ -147,11 +148,11 @@ enum RegorusCommand {
|
||||
#[arg(long, short)]
|
||||
trace: bool,
|
||||
|
||||
// Non strict execution
|
||||
/// Perform non-strict evaluation. (default behavior of OPA).
|
||||
#[arg(long, short)]
|
||||
non_strict: bool,
|
||||
|
||||
// Display coverage information
|
||||
/// Display coverage information
|
||||
#[cfg(feature = "coverage")]
|
||||
#[arg(long, short)]
|
||||
coverage: bool,
|
||||
|
||||
@@ -17,10 +17,12 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
|
||||
m.insert("print", (print, MAX_ARGS));
|
||||
}
|
||||
|
||||
// Symbol analyzer must ensure that vars used by print are defined before
|
||||
// the print statement. Scheduler must ensure the above constraint.
|
||||
// Additionally interpreter must allow undefined inputs to print.
|
||||
fn print(span: &Span, _params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
||||
pub fn print_to_string(
|
||||
span: &Span,
|
||||
_params: &[Ref<Expr>],
|
||||
args: &[Value],
|
||||
_strict: bool,
|
||||
) -> Result<String> {
|
||||
if args.len() > MAX_ARGS as usize {
|
||||
bail!(span.error("print supports up to 100 arguments"));
|
||||
}
|
||||
@@ -34,6 +36,15 @@ fn print(span: &Span, _params: &[Ref<Expr>], args: &[Value], _strict: bool) -> R
|
||||
};
|
||||
}
|
||||
|
||||
Ok(msg)
|
||||
}
|
||||
|
||||
// Symbol analyzer must ensure that vars used by print are defined before
|
||||
// the print statement. Scheduler must ensure the above constraint.
|
||||
// Additionally interpreter must allow undefined inputs to print.
|
||||
fn print(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) -> Result<Value> {
|
||||
let msg = print_to_string(span, params, args, strict)?;
|
||||
|
||||
if !msg.is_empty() {
|
||||
eprintln!("{}", &msg[1..]);
|
||||
}
|
||||
|
||||
@@ -54,6 +54,8 @@ use lazy_static::lazy_static;
|
||||
|
||||
pub type BuiltinFcn = (fn(&Span, &[Ref<Expr>], &[Value], bool) -> Result<Value>, u8);
|
||||
|
||||
pub use debugging::print_to_string;
|
||||
|
||||
#[cfg(feature = "deprecated")]
|
||||
pub use deprecated::DEPRECATED;
|
||||
|
||||
|
||||
@@ -599,66 +599,82 @@ mod tests {
|
||||
for (input, expected_dur) in [
|
||||
// simple
|
||||
("0", Duration::zero()),
|
||||
("5s", Duration::seconds(5)),
|
||||
("30s", Duration::seconds(30)),
|
||||
("1478s", Duration::seconds(1478)),
|
||||
("5s", Duration::try_seconds(5).unwrap()),
|
||||
("30s", Duration::try_seconds(30).unwrap()),
|
||||
("1478s", Duration::try_seconds(1478).unwrap()),
|
||||
// sign
|
||||
("-5s", -Duration::seconds(5)),
|
||||
("+5s", Duration::seconds(5)),
|
||||
("-5s", -Duration::try_seconds(5).unwrap()),
|
||||
("+5s", Duration::try_seconds(5).unwrap()),
|
||||
("-0", Duration::zero()),
|
||||
("+0", Duration::zero()),
|
||||
// decimal
|
||||
("5.0s", Duration::seconds(5)),
|
||||
("5.6s", Duration::seconds(5) + Duration::milliseconds(600)),
|
||||
("5.s", Duration::seconds(5)),
|
||||
(".5s", Duration::milliseconds(500)),
|
||||
("1.0s", Duration::seconds(1)),
|
||||
("1.00s", Duration::seconds(1)),
|
||||
("1.004s", Duration::seconds(1) + Duration::milliseconds(4)),
|
||||
("1.0040s", Duration::seconds(1) + Duration::milliseconds(4)),
|
||||
("5.0s", Duration::try_seconds(5).unwrap()),
|
||||
(
|
||||
"5.6s",
|
||||
Duration::try_seconds(5).unwrap() + Duration::try_milliseconds(600).unwrap(),
|
||||
),
|
||||
("5.s", Duration::try_seconds(5).unwrap()),
|
||||
(".5s", Duration::try_milliseconds(500).unwrap()),
|
||||
("1.0s", Duration::try_seconds(1).unwrap()),
|
||||
("1.00s", Duration::try_seconds(1).unwrap()),
|
||||
(
|
||||
"1.004s",
|
||||
Duration::try_seconds(1).unwrap() + Duration::try_milliseconds(4).unwrap(),
|
||||
),
|
||||
(
|
||||
"1.0040s",
|
||||
Duration::try_seconds(1).unwrap() + Duration::try_milliseconds(4).unwrap(),
|
||||
),
|
||||
(
|
||||
"100.00100s",
|
||||
Duration::seconds(100) + Duration::milliseconds(1),
|
||||
Duration::try_seconds(100).unwrap() + Duration::try_milliseconds(1).unwrap(),
|
||||
),
|
||||
// different units
|
||||
("10ns", Duration::nanoseconds(10)),
|
||||
("11us", Duration::microseconds(11)),
|
||||
("12µs", Duration::microseconds(12)), // U+00B5
|
||||
("12μs", Duration::microseconds(12)), // U+03BC
|
||||
("13ms", Duration::milliseconds(13)),
|
||||
("14s", Duration::seconds(14)),
|
||||
("15m", Duration::minutes(15)),
|
||||
("16h", Duration::hours(16)),
|
||||
("13ms", Duration::try_milliseconds(13).unwrap()),
|
||||
("14s", Duration::try_seconds(14).unwrap()),
|
||||
("15m", Duration::try_minutes(15).unwrap()),
|
||||
("16h", Duration::try_hours(16).unwrap()),
|
||||
// composite durations
|
||||
("3h30m", Duration::hours(3) + Duration::minutes(30)),
|
||||
(
|
||||
"3h30m",
|
||||
Duration::try_hours(3).unwrap() + Duration::try_minutes(30).unwrap(),
|
||||
),
|
||||
(
|
||||
"10.5s4m",
|
||||
Duration::minutes(4) + Duration::seconds(10) + Duration::milliseconds(500),
|
||||
Duration::try_minutes(4).unwrap()
|
||||
+ Duration::try_seconds(10).unwrap()
|
||||
+ Duration::try_milliseconds(500).unwrap(),
|
||||
),
|
||||
(
|
||||
"-2m3.4s",
|
||||
-(Duration::minutes(2) + Duration::seconds(3) + Duration::milliseconds(400)),
|
||||
-(Duration::try_minutes(2).unwrap()
|
||||
+ Duration::try_seconds(3).unwrap()
|
||||
+ Duration::try_milliseconds(400).unwrap()),
|
||||
),
|
||||
(
|
||||
"1h2m3s4ms5us6ns",
|
||||
Duration::hours(1)
|
||||
+ Duration::minutes(2)
|
||||
+ Duration::seconds(3)
|
||||
+ Duration::milliseconds(4)
|
||||
Duration::try_hours(1).unwrap()
|
||||
+ Duration::try_minutes(2).unwrap()
|
||||
+ Duration::try_seconds(3).unwrap()
|
||||
+ Duration::try_milliseconds(4).unwrap()
|
||||
+ Duration::microseconds(5)
|
||||
+ Duration::nanoseconds(6),
|
||||
),
|
||||
(
|
||||
"39h9m14.425s",
|
||||
Duration::hours(39)
|
||||
+ Duration::minutes(9)
|
||||
+ Duration::seconds(14)
|
||||
+ Duration::milliseconds(425),
|
||||
Duration::try_hours(39).unwrap()
|
||||
+ Duration::try_minutes(9).unwrap()
|
||||
+ Duration::try_seconds(14).unwrap()
|
||||
+ Duration::try_milliseconds(425).unwrap(),
|
||||
),
|
||||
// large value
|
||||
("52763797000ns", Duration::nanoseconds(52763797000)),
|
||||
// more than 9 digits after decimal point, see https://golang.org/issue/6617
|
||||
("0.3333333333333333333h", Duration::minutes(20)),
|
||||
("0.3333333333333333333h", Duration::try_minutes(20).unwrap()),
|
||||
// 9007199254740993 = 1<<53+1 cannot be stored precisely in a float64
|
||||
("9007199254740993ns", Duration::nanoseconds((1 << 53) + 1)),
|
||||
// largest duration that can be represented by int64 in nanoseconds
|
||||
@@ -679,11 +695,16 @@ mod tests {
|
||||
// largest negative round trip value, see https://golang.org/issue/48629
|
||||
("-2562047h47m16.854775808s", Duration::nanoseconds(i64::MIN)),
|
||||
// huge string; issue 15011.
|
||||
("0.100000000000000000000h", Duration::minutes(6)),
|
||||
(
|
||||
"0.100000000000000000000h",
|
||||
Duration::try_minutes(6).unwrap(),
|
||||
),
|
||||
// This value tests the first overflow check in leadingFraction.
|
||||
(
|
||||
"0.830103483285477580700h",
|
||||
Duration::minutes(49) + Duration::seconds(48) + Duration::nanoseconds(372539827),
|
||||
Duration::try_minutes(49).unwrap()
|
||||
+ Duration::try_seconds(48).unwrap()
|
||||
+ Duration::nanoseconds(372539827),
|
||||
),
|
||||
] {
|
||||
let dur = parse_duration(input).unwrap();
|
||||
|
||||
@@ -241,9 +241,12 @@ impl Engine {
|
||||
/// assert_eq!(results.result[0].expressions[0].value, Value::from(true));
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn eval_query(&mut self, query: String, enable_tracing: bool) -> Result<QueryResults> {
|
||||
self.eval_modules(enable_tracing)?;
|
||||
self.prepare_for_eval(enable_tracing)?;
|
||||
self.interpreter.clean_internal_evaluation_state();
|
||||
|
||||
self.interpreter.create_rule_prefixes()?;
|
||||
let query_module = {
|
||||
let source = Source::new(
|
||||
"<query_module.rego>".to_owned(),
|
||||
@@ -256,6 +259,9 @@ impl Engine {
|
||||
let query_source = Source::new("<query.rego>".to_string(), query);
|
||||
let mut parser = Parser::new(&query_source)?;
|
||||
let query_node = parser.parse_user_query()?;
|
||||
if query_node.span.text() == "data" {
|
||||
self.eval_modules(enable_tracing)?;
|
||||
}
|
||||
let query_schedule = Analyzer::new().analyze_query_snippet(&self.modules, &query_node)?;
|
||||
self.interpreter.eval_user_query(
|
||||
&query_module,
|
||||
@@ -290,6 +296,7 @@ impl Engine {
|
||||
/// assert!(engine.eval_bool_query("true; false; true".to_string(), enable_tracing).is_err());
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn eval_bool_query(&mut self, query: String, enable_tracing: bool) -> Result<bool> {
|
||||
let results = self.eval_query(query, enable_tracing)?;
|
||||
match results.result.len() {
|
||||
@@ -346,6 +353,38 @@ impl Engine {
|
||||
!matches!(self.eval_bool_query(query, enable_tracing), Ok(false))
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Evaluate the given query and all the rules in the supplied policies.
|
||||
///
|
||||
/// This is mainly used for testing Regorus itself.
|
||||
pub fn eval_query_and_all_rules(
|
||||
&mut self,
|
||||
query: String,
|
||||
enable_tracing: bool,
|
||||
) -> Result<QueryResults> {
|
||||
self.eval_modules(enable_tracing)?;
|
||||
|
||||
let query_module = {
|
||||
let source = Source::new(
|
||||
"<query_module.rego>".to_owned(),
|
||||
"package __internal_query_module".to_owned(),
|
||||
);
|
||||
Ref::new(Parser::new(&source)?.parse()?)
|
||||
};
|
||||
|
||||
// Parse the query.
|
||||
let query_source = Source::new("<query.rego>".to_string(), query);
|
||||
let mut parser = Parser::new(&query_source)?;
|
||||
let query_node = parser.parse_user_query()?;
|
||||
let query_schedule = Analyzer::new().analyze_query_snippet(&self.modules, &query_node)?;
|
||||
self.interpreter.eval_user_query(
|
||||
&query_module,
|
||||
&query_node,
|
||||
&query_schedule,
|
||||
enable_tracing,
|
||||
)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
fn prepare_for_eval(&mut self, enable_tracing: bool) -> Result<()> {
|
||||
self.interpreter.set_traces(enable_tracing);
|
||||
@@ -513,8 +552,8 @@ impl Engine {
|
||||
/// "#.to_string()
|
||||
/// )?;
|
||||
///
|
||||
/// // Evaluation fails since y is not defined.
|
||||
/// assert!(engine.eval_query("data.invalid.y".to_string(), false).is_err());
|
||||
/// // Evaluation fails since rule x calls an extension with out parameter.
|
||||
/// assert!(engine.eval_query("data.invalid.x".to_string(), false).is_err());
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
@@ -587,4 +626,43 @@ impl Engine {
|
||||
pub fn clear_coverage_data(&mut self) {
|
||||
self.interpreter.clear_coverage_data()
|
||||
}
|
||||
|
||||
/// Gather output from print statements instead of emiting to stderr.
|
||||
///
|
||||
/// See [`Engine::take_prints`].
|
||||
pub fn set_gather_prints(&mut self, b: bool) {
|
||||
self.interpreter.set_gather_prints(b);
|
||||
}
|
||||
|
||||
/// Take the gathered output of print statements.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use regorus::*;
|
||||
/// # use anyhow::{bail, Result};
|
||||
/// # fn main() -> Result<()> {
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Print to stderr.
|
||||
/// engine.eval_query("print(\"Hello\")".to_string(), false)?;
|
||||
///
|
||||
/// // Configure gathering print statements.
|
||||
/// engine.set_gather_prints(true);
|
||||
///
|
||||
/// // Execute query.
|
||||
/// engine.eval_query("print(\"Hello\")".to_string(), false)?;
|
||||
///
|
||||
/// // Take and clear prints.
|
||||
/// let prints = engine.take_prints()?;
|
||||
/// assert_eq!(prints.len(), 1);
|
||||
/// assert!(prints[0].contains("Hello"));
|
||||
///
|
||||
/// for p in prints {
|
||||
/// println!("{p}");
|
||||
/// }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn take_prints(&mut self) -> Result<Vec<String>> {
|
||||
self.interpreter.take_prints()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ type State = (
|
||||
Value,
|
||||
Value,
|
||||
BTreeSet<Ref<Rule>>,
|
||||
Value,
|
||||
BTreeMap<String, FunctionModifier>,
|
||||
BTreeMap<Vec<Value>, (Value, Ref<Expr>)>,
|
||||
);
|
||||
@@ -57,6 +58,7 @@ pub struct Interpreter {
|
||||
rules: HashMap<String, Vec<Ref<Rule>>>,
|
||||
default_rules: HashMap<String, Vec<DefaultRuleInfo>>,
|
||||
processed: BTreeSet<Ref<Rule>>,
|
||||
processed_paths: Value,
|
||||
rule_values: BTreeMap<Vec<Value>, (Value, Ref<Expr>)>,
|
||||
active_rules: Vec<Ref<Rule>>,
|
||||
builtins_cache: BTreeMap<(&'static str, Vec<Value>), Value>,
|
||||
@@ -71,6 +73,9 @@ pub struct Interpreter {
|
||||
coverage: HashMap<Source, Vec<bool>>,
|
||||
#[cfg(feature = "coverage")]
|
||||
enable_coverage: bool,
|
||||
|
||||
gather_prints: bool,
|
||||
prints: Vec<String>,
|
||||
}
|
||||
|
||||
impl Default for Interpreter {
|
||||
@@ -173,6 +178,7 @@ impl Interpreter {
|
||||
rules: HashMap::new(),
|
||||
default_rules: HashMap::new(),
|
||||
processed: BTreeSet::new(),
|
||||
processed_paths: Value::new_object(),
|
||||
rule_values: BTreeMap::new(),
|
||||
active_rules: vec![],
|
||||
builtins_cache: BTreeMap::new(),
|
||||
@@ -187,6 +193,9 @@ impl Interpreter {
|
||||
coverage: HashMap::new(),
|
||||
#[cfg(feature = "coverage")]
|
||||
enable_coverage: false,
|
||||
|
||||
gather_prints: false,
|
||||
prints: Vec::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,6 +253,7 @@ impl Interpreter {
|
||||
pub fn clean_internal_evaluation_state(&mut self) {
|
||||
self.data = self.init_data.clone();
|
||||
self.processed.clear();
|
||||
self.processed_paths = Value::new_object();
|
||||
self.loop_var_values.clear();
|
||||
self.scopes = vec![Scope::new()];
|
||||
self.contexts = vec![];
|
||||
@@ -1189,6 +1199,7 @@ impl Interpreter {
|
||||
let rule_values = self.rule_values.clone();
|
||||
|
||||
self.processed.clear();
|
||||
let processed_paths = std::mem::replace(&mut self.processed_paths, Value::new_object());
|
||||
self.rule_values.clear();
|
||||
|
||||
let mut skip_exec = false;
|
||||
@@ -1285,6 +1296,7 @@ impl Interpreter {
|
||||
input,
|
||||
data,
|
||||
processed,
|
||||
processed_paths,
|
||||
with_functions,
|
||||
rule_values,
|
||||
)),
|
||||
@@ -1302,6 +1314,7 @@ impl Interpreter {
|
||||
self.input,
|
||||
self.data,
|
||||
self.processed,
|
||||
self.processed_paths,
|
||||
self.with_functions,
|
||||
self.rule_values,
|
||||
) = s;
|
||||
@@ -1898,7 +1911,13 @@ impl Interpreter {
|
||||
// ( scalar | ref | var ) ":" term, the OPA
|
||||
// implementation is more like expr ":" expr
|
||||
let key = self.eval_expr(key)?;
|
||||
if key == Value::Undefined {
|
||||
return Ok(Value::Undefined);
|
||||
}
|
||||
let value = self.eval_expr(value)?;
|
||||
if value == Value::Undefined {
|
||||
return Ok(Value::Undefined);
|
||||
}
|
||||
object.insert(key, value);
|
||||
}
|
||||
|
||||
@@ -2039,7 +2058,8 @@ impl Interpreter {
|
||||
params: &[ExprRef],
|
||||
) -> Result<Value> {
|
||||
let mut args = vec![];
|
||||
let allow_undefined = name == "print"; // TODO: with modifier
|
||||
let is_print = name == "print"; // TODO: with modifier
|
||||
let allow_undefined = is_print;
|
||||
for p in params {
|
||||
match self.eval_expr(p)? {
|
||||
// If any argument is undefined, then the call is undefined.
|
||||
@@ -2048,6 +2068,17 @@ impl Interpreter {
|
||||
}
|
||||
}
|
||||
|
||||
if is_print && self.gather_prints {
|
||||
// Do not print to stderr. Instead, gather.
|
||||
let msg =
|
||||
builtins::print_to_string(span, params, &args[..], self.strict_builtin_errors)?;
|
||||
|
||||
// Prefix location information.
|
||||
self.prints
|
||||
.push(format!("{}:{}: {msg}", span.source.file(), span.line));
|
||||
return Ok(Value::Bool(true));
|
||||
}
|
||||
|
||||
let cache = builtins::must_cache(name);
|
||||
if let Some(name) = &cache {
|
||||
if let Some(v) = self.builtins_cache.get(&(name, args.clone())) {
|
||||
@@ -2424,13 +2455,11 @@ impl Interpreter {
|
||||
|| &module_path[path.len()..path.len() + 1] == ".")
|
||||
{
|
||||
// Ensure that the module is created.
|
||||
{
|
||||
let path = Parser::get_path_ref_components(&module.package.refr)?;
|
||||
let path: Vec<&str> = path.iter().map(|s| s.text()).collect();
|
||||
let vref = Self::make_or_get_value_mut(&mut self.data, &path[..])?;
|
||||
if *vref == Value::Undefined {
|
||||
*vref = Value::new_object();
|
||||
}
|
||||
let path = Parser::get_path_ref_components(&module.package.refr)?;
|
||||
let path: Vec<&str> = path.iter().map(|s| s.text()).collect();
|
||||
let vref = Self::make_or_get_value_mut(&mut self.data, &path[..])?;
|
||||
if *vref == Value::Undefined {
|
||||
*vref = Value::new_object();
|
||||
}
|
||||
|
||||
for rule in &module.policy {
|
||||
@@ -2446,13 +2475,17 @@ impl Interpreter {
|
||||
}
|
||||
}
|
||||
self.set_current_module(prev_module)?;
|
||||
self.mark_processed(&path)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ensure_rule_evaluated(&mut self, path: String) -> Result<()> {
|
||||
let mut matched = false;
|
||||
if let Some(rules) = self.rules.get(&path) {
|
||||
matched = true;
|
||||
for r in rules.clone() {
|
||||
if !self.processed.contains(&r) {
|
||||
let module = self.get_rule_module(&r)?;
|
||||
@@ -2460,8 +2493,10 @@ impl Interpreter {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate the associated default rules after non-default rules
|
||||
if let Some(rules) = self.default_rules.get(&path) {
|
||||
matched = true;
|
||||
for (r, _) in rules.clone() {
|
||||
if !self.processed.contains(&r) {
|
||||
let module = self.get_rule_module(&r)?;
|
||||
@@ -2472,6 +2507,37 @@ impl Interpreter {
|
||||
}
|
||||
}
|
||||
|
||||
if matched {
|
||||
let comps: Vec<&str> = path.split('.').collect();
|
||||
self.mark_processed(&comps[1..])?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_processed(&self, path: &[&str]) -> Result<bool> {
|
||||
let mut obj = &self.processed_paths;
|
||||
for p in path {
|
||||
// Prefix has already been processed.
|
||||
if obj[&Value::Undefined] == Value::Null {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
match &obj[*p] {
|
||||
// Prefix and its suffixes including path have not been processed.
|
||||
Value::Undefined => return Ok(false),
|
||||
v => obj = v,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(obj[&Value::Undefined] == Value::Null)
|
||||
}
|
||||
|
||||
fn mark_processed(&mut self, path: &[&str]) -> Result<()> {
|
||||
let obj = self.processed_paths.make_or_get_value_mut(path)?;
|
||||
if obj == &Value::Undefined {
|
||||
*obj = Value::new_object();
|
||||
}
|
||||
obj.as_object_mut()?.insert(Value::Undefined, Value::Null);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -2499,50 +2565,41 @@ impl Interpreter {
|
||||
|
||||
// Ensure that rules are evaluated
|
||||
if name.text() == "data" {
|
||||
if self.is_processed(fields)? {
|
||||
return Ok(Self::get_value_chained(self.data.clone(), fields));
|
||||
}
|
||||
|
||||
// If "data" is used in a query, without any fields, then evaluate all the modules.
|
||||
if fields.is_empty() && self.active_rules.is_empty() {
|
||||
for module in self.modules.clone() {
|
||||
for rule in &module.policy {
|
||||
self.eval_rule(&module, rule)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// With modifiers may be used to specify part of a module that that not yet been
|
||||
// evaluated. Therefore ensure that module is evaluated first.
|
||||
let path = "data.".to_owned() + &fields.join(".");
|
||||
self.ensure_module_evaluated(path)?;
|
||||
self.ensure_module_evaluated(path.clone())?;
|
||||
|
||||
// If the rule has already been evaluated or specified via a with modifier,
|
||||
// use that value.
|
||||
let v = Self::get_value_chained(self.data.clone(), fields);
|
||||
|
||||
if v != Value::Undefined {
|
||||
debug!("returning v = {v}");
|
||||
return Ok(v);
|
||||
}
|
||||
|
||||
// Find the rule to which the var being looked up corresponds to. This is the prefix for
|
||||
// which rules exist.
|
||||
let mut found = false;
|
||||
for i in (1..fields.len() + 1).rev() {
|
||||
let path = "data.".to_owned() + &fields[0..i].join(".");
|
||||
if self.rules.get(&path).is_some() || self.default_rules.get(&path).is_some() {
|
||||
self.ensure_rule_evaluated(path)?;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
// This could be path to a module.
|
||||
let path = "data.".to_owned() + &fields.join(".");
|
||||
self.ensure_module_evaluated(path)?;
|
||||
}
|
||||
|
||||
Ok(Self::get_value_chained(self.data.clone(), fields))
|
||||
} else if !self.modules.is_empty() {
|
||||
let path = Parser::get_path_ref_components(&self.module.clone().unwrap().package.refr)?;
|
||||
let mut path: Vec<&str> = path.iter().map(|s| s.text()).collect();
|
||||
path.push(name.text());
|
||||
|
||||
let v = Self::get_value_chained(self.data.clone(), &path);
|
||||
|
||||
// If the rule has already been evaluated or specified via a with modifier,
|
||||
// use that value.
|
||||
if v != Value::Undefined {
|
||||
return Ok(Self::get_value_chained(v, fields));
|
||||
if self.is_processed(&path)? {
|
||||
let value = Self::get_value_chained(self.data.clone(), &path);
|
||||
return Ok(Self::get_value_chained(value, fields));
|
||||
}
|
||||
|
||||
// Ensure that all the rules having common prefix (name) are evaluated.
|
||||
@@ -2673,7 +2730,22 @@ impl Interpreter {
|
||||
key, value, query, ..
|
||||
} => self.eval_object_compr(key, value, query),
|
||||
Expr::SetCompr { term, query, .. } => self.eval_set_compr(term, query),
|
||||
Expr::UnaryExpr { .. } => unimplemented!("unar expr is umplemented"),
|
||||
Expr::UnaryExpr { span, expr: uexpr } => match uexpr.as_ref() {
|
||||
Expr::Number(_) if !uexpr.span().text().starts_with('-') => {
|
||||
builtins::numbers::arithmetic_operation(
|
||||
span,
|
||||
&ArithOp::Sub,
|
||||
expr,
|
||||
uexpr,
|
||||
Value::from(0),
|
||||
self.eval_expr(uexpr)?,
|
||||
self.strict_builtin_errors,
|
||||
)
|
||||
}
|
||||
_ => bail!(expr
|
||||
.span()
|
||||
.error("unary - can only be used with numeric literals")),
|
||||
},
|
||||
Expr::Call { span, fcn, params } => {
|
||||
self.eval_call(span, expr, fcn, params, None, false)
|
||||
}
|
||||
@@ -2854,25 +2926,23 @@ impl Interpreter {
|
||||
pub fn get_path_string(refr: &Expr, document: Option<&str>) -> Result<String> {
|
||||
let mut comps = vec![];
|
||||
let mut expr = Some(refr);
|
||||
while expr.is_some() {
|
||||
match expr {
|
||||
Some(Expr::RefDot { refr, field, .. }) => {
|
||||
while let Some(e) = expr {
|
||||
match e {
|
||||
Expr::RefDot { refr, field, .. } => {
|
||||
comps.push(field.text());
|
||||
expr = Some(refr);
|
||||
}
|
||||
Some(Expr::RefBrack { refr, index, .. })
|
||||
if matches!(index.as_ref(), Expr::String(_)) =>
|
||||
{
|
||||
Expr::RefBrack { refr, index, .. } if matches!(index.as_ref(), Expr::String(_)) => {
|
||||
if let Expr::String(s) = index.as_ref() {
|
||||
comps.push(s.text());
|
||||
expr = Some(refr);
|
||||
}
|
||||
}
|
||||
Some(Expr::Var(v)) => {
|
||||
Expr::Var(v) => {
|
||||
comps.push(v.text());
|
||||
expr = None;
|
||||
}
|
||||
_ => bail!(format!("internal error: not a simplee ref {expr:?}")),
|
||||
_ => bail!(e.span().error("invalid ref expression")),
|
||||
}
|
||||
}
|
||||
if let Some(d) = document {
|
||||
@@ -3340,31 +3410,29 @@ impl Interpreter {
|
||||
debug!("processing module {module_path:?}");
|
||||
|
||||
for rule in &module.policy {
|
||||
let mut rule_refr = Self::get_rule_refr(rule);
|
||||
debug!("rule refr: {}", rule_refr.span().text());
|
||||
debug!("rule : {:?}", rule);
|
||||
if let Rule::Spec {
|
||||
head:
|
||||
RuleHead::Set {
|
||||
refr, key: None, ..
|
||||
},
|
||||
..
|
||||
} = rule.as_ref()
|
||||
{
|
||||
rule_refr = match refr.as_ref() {
|
||||
Expr::RefDot { refr, .. } => refr,
|
||||
|
||||
_ => refr,
|
||||
let rule_refr = Self::get_rule_refr(rule);
|
||||
let mut prefix_path = module_path.clone();
|
||||
let mut components = Self::get_rule_path_components(rule_refr)?;
|
||||
let is_old_set = matches!(
|
||||
rule.as_ref(),
|
||||
Rule::Spec {
|
||||
head: RuleHead::Set { key: None, .. },
|
||||
..
|
||||
}
|
||||
);
|
||||
|
||||
if components.len() >= 2 && is_old_set {
|
||||
components.pop();
|
||||
}
|
||||
|
||||
let mut prefix_path = module_path.clone();
|
||||
prefix_path.append(&mut Self::get_rule_path_components(rule_refr)?);
|
||||
let prefix_path: Vec<&str> = prefix_path[0..prefix_path.len() - 1]
|
||||
.iter()
|
||||
.map(|s| s.as_ref())
|
||||
.collect();
|
||||
if components.len() > 1 {
|
||||
components.pop();
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
prefix_path.append(&mut components);
|
||||
let prefix_path: Vec<&str> = prefix_path.iter().map(|s| s.as_ref()).collect();
|
||||
if Self::get_value_chained(self.data.clone(), &prefix_path) == Value::Undefined {
|
||||
self.update_data(
|
||||
rule_refr.span(),
|
||||
@@ -3398,6 +3466,42 @@ impl Interpreter {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn record_default_rule(
|
||||
&mut self,
|
||||
refr: &Ref<Expr>,
|
||||
rule: &Ref<Rule>,
|
||||
index: Option<String>,
|
||||
) -> Result<()> {
|
||||
let comps = Parser::get_path_ref_components(refr)?;
|
||||
let comps: Vec<&str> = comps.iter().map(|s| s.text()).collect();
|
||||
for (idx, c) in (0..comps.len()).enumerate() {
|
||||
let path = self.current_module_path.clone() + "." + &comps[0..c + 1].join(".");
|
||||
match self.default_rules.entry(path) {
|
||||
Entry::Occupied(o) => {
|
||||
if idx + 1 == comps.len() {
|
||||
for (_, i) in o.get() {
|
||||
if index.is_some() && i.is_some() {
|
||||
let old = i.as_ref().unwrap();
|
||||
let new = index.as_ref().unwrap();
|
||||
if old == new {
|
||||
bail!(refr.span().error("multiple default rules for the variable with the same index"));
|
||||
}
|
||||
} else if index.is_some() || i.is_some() {
|
||||
bail!(refr.span().error("conflict type with the default rules"));
|
||||
}
|
||||
}
|
||||
}
|
||||
o.into_mut().push((rule.clone(), index.clone()));
|
||||
}
|
||||
Entry::Vacant(v) => {
|
||||
v.insert(vec![(rule.clone(), index.clone())]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process_imports(&mut self) -> Result<()> {
|
||||
for module in &self.modules {
|
||||
let module_path = get_path_string(&module.package.refr, Some("data"))?;
|
||||
@@ -3414,7 +3518,10 @@ impl Interpreter {
|
||||
// Warn redundant import of input. Ignore it.
|
||||
eprintln!(
|
||||
"{}",
|
||||
import.refr.span().error("redundant import of `input`")
|
||||
import
|
||||
.refr
|
||||
.span()
|
||||
.message("warning", "redundant import of `input`")
|
||||
);
|
||||
continue;
|
||||
}
|
||||
@@ -3472,29 +3579,7 @@ impl Interpreter {
|
||||
_ => (refr, None),
|
||||
};
|
||||
|
||||
let path = Self::get_path_string(refr, None)?;
|
||||
let path = self.current_module_path.clone() + "." + &path;
|
||||
match self.default_rules.entry(path) {
|
||||
Entry::Occupied(o) => {
|
||||
for (_, i) in o.get() {
|
||||
if index.is_some() && i.is_some() {
|
||||
let old = i.as_ref().unwrap();
|
||||
let new = index.as_ref().unwrap();
|
||||
if old == new {
|
||||
bail!(refr.span().error("multiple default rules for the variable with the same index"));
|
||||
}
|
||||
} else if index.is_some() || i.is_some() {
|
||||
bail!(refr
|
||||
.span()
|
||||
.error("conflict type with the default rules"));
|
||||
}
|
||||
}
|
||||
o.into_mut().push((rule.clone(), index));
|
||||
}
|
||||
Entry::Vacant(v) => {
|
||||
v.insert(vec![(rule.clone(), index)]);
|
||||
}
|
||||
}
|
||||
self.record_default_rule(refr, rule, index)?;
|
||||
}
|
||||
}
|
||||
self.set_current_module(prev_module)?;
|
||||
@@ -3636,4 +3721,16 @@ impl Interpreter {
|
||||
pub fn clear_coverage_data(&mut self) {
|
||||
self.coverage = HashMap::new();
|
||||
}
|
||||
|
||||
pub fn set_gather_prints(&mut self, b: bool) {
|
||||
if b != self.gather_prints {
|
||||
// Clear existing prints.
|
||||
std::mem::take(&mut self.prints);
|
||||
}
|
||||
self.gather_prints = b;
|
||||
}
|
||||
|
||||
pub fn take_prints(&mut self) -> Result<Vec<String>> {
|
||||
Ok(std::mem::take(&mut self.prints))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ use std::rc::Rc;
|
||||
/// # }
|
||||
/// ````
|
||||
/// See also [`QueryResult`].
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[derive(Debug, Clone, Serialize, Eq, PartialEq)]
|
||||
pub struct Location {
|
||||
/// Line number. Starts at 1.
|
||||
pub row: u16,
|
||||
@@ -69,7 +69,7 @@ pub struct Location {
|
||||
/// # }
|
||||
/// ```
|
||||
/// See also [`QueryResult`].
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[derive(Debug, Clone, Serialize, Eq, PartialEq)]
|
||||
pub struct Expression {
|
||||
/// Computed value of the expression.
|
||||
pub value: Value,
|
||||
@@ -157,7 +157,7 @@ pub struct Expression {
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[derive(Debug, Clone, Serialize, Eq, PartialEq)]
|
||||
pub struct QueryResult {
|
||||
/// Expressions in the query.
|
||||
///
|
||||
@@ -296,7 +296,7 @@ impl Default for QueryResult {
|
||||
/// ```
|
||||
///
|
||||
/// See [QueryResult] for examples of different kinds of results.
|
||||
#[derive(Debug, Clone, Default, Serialize)]
|
||||
#[derive(Debug, Clone, Default, Serialize, Eq, PartialEq)]
|
||||
pub struct QueryResults {
|
||||
/// Collection of results of evaluting a query.
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
|
||||
@@ -162,31 +162,48 @@ pub fn eval_file(
|
||||
engine.add_data(data)?;
|
||||
}
|
||||
|
||||
if let Some(input) = input_opt {
|
||||
// all modules are evaluated for each input
|
||||
let mut inputs = vec![];
|
||||
match input {
|
||||
ValueOrVec::Single(single_input) => inputs.push(single_input),
|
||||
ValueOrVec::Many(mut many_input) => inputs.append(&mut many_input),
|
||||
let mut inputs = vec![];
|
||||
match input_opt {
|
||||
Some(ValueOrVec::Single(single_input)) => inputs.push(single_input),
|
||||
Some(ValueOrVec::Many(mut many_input)) => inputs.append(&mut many_input),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let mut engine_full = engine.clone();
|
||||
|
||||
if inputs.is_empty() {
|
||||
// Now eval the query.
|
||||
let r = engine.eval_query(query.to_string(), enable_tracing)?;
|
||||
let r_full = engine_full.eval_query_and_all_rules(query.to_string(), enable_tracing)?;
|
||||
if r != r_full {
|
||||
println!(
|
||||
"{}\n{}",
|
||||
serde_json::to_string_pretty(&r_full)?,
|
||||
serde_json::to_string_pretty(&r)?
|
||||
);
|
||||
assert_eq!(r_full, r);
|
||||
}
|
||||
|
||||
push_query_results(r, &mut results);
|
||||
} else {
|
||||
for input in inputs {
|
||||
engine.set_input(input);
|
||||
engine.eval_modules(enable_tracing)?;
|
||||
engine.set_input(input.clone());
|
||||
engine_full.set_input(input);
|
||||
|
||||
// Now eval the query.
|
||||
push_query_results(
|
||||
engine.eval_query(query.to_string(), enable_tracing)?,
|
||||
&mut results,
|
||||
);
|
||||
let r = engine.eval_query(query.to_string(), enable_tracing)?;
|
||||
let r_full = engine_full.eval_query_and_all_rules(query.to_string(), enable_tracing)?;
|
||||
if r != r_full {
|
||||
println!(
|
||||
"{}\n{}",
|
||||
serde_json::to_string_pretty(&r_full)?,
|
||||
serde_json::to_string_pretty(&r)?
|
||||
);
|
||||
assert_eq!(r_full, r);
|
||||
}
|
||||
|
||||
push_query_results(r, &mut results);
|
||||
}
|
||||
} else {
|
||||
// it no input is defined then one evaluation of all modules is performed
|
||||
// Now eval the query.
|
||||
push_query_results(
|
||||
engine.eval_query(query.to_string(), enable_tracing)?,
|
||||
&mut results,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(results)
|
||||
|
||||
@@ -190,11 +190,12 @@ pub fn gather_functions(modules: &[Ref<Module>]) -> Result<FunctionTable> {
|
||||
}
|
||||
|
||||
pub fn get_root_var(mut expr: &Expr) -> Result<SourceStr> {
|
||||
let empty = expr.span().source_str().clone_empty();
|
||||
loop {
|
||||
match expr {
|
||||
Expr::Var(v) => return Ok(v.source_str()),
|
||||
Expr::RefDot { refr, .. } | Expr::RefBrack { refr, .. } => expr = refr,
|
||||
_ => bail!("internal error: analyzer: could not get rule prefix"),
|
||||
_ => return Ok(empty),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,8 +41,13 @@ fn eval_test_case(dir: &Path, case: &TestCase) -> Result<Value> {
|
||||
}
|
||||
}
|
||||
|
||||
let mut engine_full = engine.clone();
|
||||
let query_results = engine.eval_query(case.query.clone(), true)?;
|
||||
|
||||
// Ensure that full evaluation produces the same results.
|
||||
let query_results_full = engine_full.eval_query_and_all_rules(case.query.clone(), true)?;
|
||||
assert_eq!(query_results, query_results_full);
|
||||
|
||||
let mut values = vec![];
|
||||
for qr in query_results.result {
|
||||
values.push(if !qr.bindings.as_object()?.is_empty() {
|
||||
|
||||
@@ -17,3 +17,38 @@
|
||||
a = to_number("abc")
|
||||
query: data.test
|
||||
error: "could not parse string as number"
|
||||
|
||||
- note: count of null error gobbled up in non strict mode
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
foo := input.a
|
||||
|
||||
some_id := {
|
||||
"count_value": count(foo) > 2,
|
||||
}
|
||||
input:
|
||||
a: null
|
||||
query: data.test
|
||||
strict: false
|
||||
want_result:
|
||||
foo: null
|
||||
|
||||
- note: count of null error in strict mode
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
foo := input.a
|
||||
|
||||
some_id := {
|
||||
"count_value": count(foo) > 2,
|
||||
(count(foo) > 2): "count_value",
|
||||
}
|
||||
input:
|
||||
a: null
|
||||
query: data.test
|
||||
error: "`count` requires array/object/set/string argument"
|
||||
|
||||
14
tests/interpreter/cases/refr/tests.yaml
Normal file
14
tests/interpreter/cases/refr/tests.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
cases:
|
||||
- note: ref
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
x = [k |
|
||||
[1, 2, 3][[1, 2, 3][k]]
|
||||
]
|
||||
|
||||
query: data.test
|
||||
want_result:
|
||||
x: [0, 1]
|
||||
@@ -39,3 +39,19 @@ cases:
|
||||
b:
|
||||
y: 10
|
||||
z: 25
|
||||
|
||||
- note: inter
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
a.b.c = 1
|
||||
a.b.d = a.b.e
|
||||
a.b.e = a.b.c
|
||||
query: data.test
|
||||
want_result:
|
||||
a:
|
||||
b:
|
||||
c: 1
|
||||
d: 1
|
||||
e: 1
|
||||
|
||||
64
tests/interpreter/cases/rule/prefix.yaml
Normal file
64
tests/interpreter/cases/rule/prefix.yaml
Normal file
@@ -0,0 +1,64 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
cases:
|
||||
- note: prefix after rules
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
a.b.c = 1
|
||||
a.b.d = 2
|
||||
y = a.b
|
||||
query: data.test
|
||||
want_result:
|
||||
a:
|
||||
b:
|
||||
c: 1
|
||||
d: 2
|
||||
y:
|
||||
c: 1
|
||||
d: 2
|
||||
|
||||
- note: prefix between rules
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
a.b.c = 1
|
||||
y = a.b
|
||||
a.b.d = 2
|
||||
query: data.test
|
||||
want_result:
|
||||
a:
|
||||
b:
|
||||
c: 1
|
||||
d: 2
|
||||
y:
|
||||
c: 1
|
||||
d: 2
|
||||
|
||||
- note: prefix between rules
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
a.b.c = 1
|
||||
y = a.b
|
||||
a.b.d = 2
|
||||
a[p][q] = 3 {
|
||||
p = "b"
|
||||
q = "e"
|
||||
}
|
||||
query: data.test
|
||||
want_result:
|
||||
a:
|
||||
b:
|
||||
c: 1
|
||||
d: 2
|
||||
e: 3
|
||||
y:
|
||||
c: 1
|
||||
d: 2
|
||||
e: 3
|
||||
skip: true
|
||||
46
tests/interpreter/cases/unary/tests.yaml
Normal file
46
tests/interpreter/cases/unary/tests.yaml
Normal file
@@ -0,0 +1,46 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
cases:
|
||||
- note: unary expr on non literals produces an error
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
x = - y
|
||||
y = 1
|
||||
query: data.test
|
||||
error: "unary - can only be used with numeric literals"
|
||||
|
||||
- note: unary expr on literals work
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
x = - 1 # With space
|
||||
y = -1
|
||||
z = - # With newlines and comment
|
||||
|
||||
1
|
||||
query: data.test
|
||||
want_result:
|
||||
x: -1
|
||||
y: -1
|
||||
z: -1
|
||||
|
||||
- note: double unary expr
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
x = - -1
|
||||
query: data.test
|
||||
error: "unary - can only be used with numeric literals"
|
||||
|
||||
- note: double unary expr double space
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
x = - - 1
|
||||
query: data.test
|
||||
error: "unary - can only be used with numeric literals"
|
||||
@@ -36,7 +36,7 @@ cases:
|
||||
true: false,
|
||||
[1, 3] : {"hello", "world"}
|
||||
}
|
||||
|
||||
|
||||
query: data.test
|
||||
want_result:
|
||||
array: [1, 2, 3]
|
||||
@@ -71,5 +71,19 @@ cases:
|
||||
set!:
|
||||
- "hello"
|
||||
- "world"
|
||||
|
||||
|
||||
|
||||
|
||||
- note: value chain (unqualified)
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a = {
|
||||
"b" : 5
|
||||
}
|
||||
x = a.b
|
||||
# The second look up must also produce the same value.
|
||||
y = a.b
|
||||
query: data.test.y
|
||||
want_result: 5
|
||||
|
||||
16
tests/opa.rs
16
tests/opa.rs
@@ -13,7 +13,7 @@ use serde::{Deserialize, Serialize};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
const OPA_REPO: &str = "https://github.com/open-policy-agent/opa";
|
||||
const OPA_BRANCH: &str = "v0.61.0";
|
||||
const OPA_BRANCH: &str = "v0.62.0";
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
@@ -83,7 +83,19 @@ fn eval_test_case(case: &TestCase) -> Result<Value> {
|
||||
|
||||
engine.set_strict_builtin_errors(case.strict_error.unwrap_or_default());
|
||||
|
||||
let query_results = engine.eval_query(case.query.clone(), true)?;
|
||||
let mut engine_full = engine.clone();
|
||||
let mut query_results = engine.eval_query(case.query.clone(), true)?;
|
||||
|
||||
// Ensure that full evaluation produces the same results.
|
||||
let qr_full = engine_full.eval_query_and_all_rules(case.query.clone(), true)?;
|
||||
if qr_full != query_results {
|
||||
if case.note == "refheads/general, set leaf, deep query" {
|
||||
// Get test to pass for now.
|
||||
query_results = qr_full;
|
||||
} else {
|
||||
println!("{}", serde_yaml::to_string(case)?);
|
||||
}
|
||||
}
|
||||
|
||||
let mut values = vec![];
|
||||
for qr in query_results.result {
|
||||
|
||||
Reference in New Issue
Block a user