mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
Minimize PR 22 (#26)
* specific functions added to eval different rego components Signed-off-by: eric-therond <eric.therond.fr@gmail.com> * allow multiple inputs and results Signed-off-by: eric-therond <eric.therond.fr@gmail.com> * prepare_for_eval is necessary to be called Signed-off-by: eric-therond <eric.therond.fr@gmail.com> * test with the suggested code examples and clean scopes Signed-off-by: eric-therond <eric.therond.fr@gmail.com> * try to refactor first steps of evaluations Signed-off-by: eric-therond <eric.therond.fr@gmail.com> * improve coverage and fix clean state internal evaluation Signed-off-by: eric-therond <eric.therond.fr@gmail.com> * add getters and setters and fix clean function Signed-off-by: eric-therond <eric.therond.fr@gmail.com> * Tests are single input by default. Multi input specified via "many!" marker. Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com> --------- Signed-off-by: eric-therond <eric.therond.fr@gmail.com> Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com> Co-authored-by: eric-therond <eric.therond.fr@gmail.com>
This commit is contained in:
committed by
GitHub
parent
2ba718ba72
commit
2436467fbd
@@ -20,8 +20,10 @@ pub struct Interpreter<'source> {
|
||||
module: Option<&'source Module<'source>>,
|
||||
schedule: Option<&'source Schedule<'source>>,
|
||||
current_module_path: String,
|
||||
prepared: bool,
|
||||
input: Value,
|
||||
data: Value,
|
||||
init_data: Value,
|
||||
scopes: Vec<Scope>,
|
||||
// TODO: handle recursive calls where same expr could have different values.
|
||||
loop_var_values: BTreeMap<&'source Expr<'source>, Value>,
|
||||
@@ -58,8 +60,10 @@ impl<'source> Interpreter<'source> {
|
||||
module: None,
|
||||
schedule: None,
|
||||
current_module_path: String::default(),
|
||||
prepared: false,
|
||||
input: Value::new_object(),
|
||||
data: Value::new_object(),
|
||||
init_data: Value::new_object(),
|
||||
scopes: vec![Scope::new()],
|
||||
contexts: vec![],
|
||||
loop_var_values: BTreeMap::new(),
|
||||
@@ -74,6 +78,44 @@ impl<'source> Interpreter<'source> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_modules(&mut self) -> &mut Vec<&'source Module<'source>> {
|
||||
&mut self.modules
|
||||
}
|
||||
|
||||
pub fn set_data(&mut self, data: Value) {
|
||||
self.data = data;
|
||||
}
|
||||
|
||||
pub fn get_data(&mut self) -> &mut Value {
|
||||
&mut self.data
|
||||
}
|
||||
|
||||
fn clean_internal_evaluation_state(&mut self) {
|
||||
self.data = self.init_data.clone();
|
||||
self.processed.clear();
|
||||
self.loop_var_values.clear();
|
||||
self.scopes = vec![Scope::new()];
|
||||
self.contexts = vec![];
|
||||
}
|
||||
|
||||
fn checks_for_eval(&mut self, input: &Option<Value>, enable_tracing: bool) -> Result<()> {
|
||||
if !self.prepared {
|
||||
bail!("prepare_for_eval should be called before eval_modules");
|
||||
}
|
||||
|
||||
self.traces = match enable_tracing {
|
||||
true => Some(vec![]),
|
||||
false => None,
|
||||
};
|
||||
|
||||
if let Some(input) = input {
|
||||
self.input = input.clone();
|
||||
info!("input: {:#?}", self.input);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn current_module(&self) -> Result<&'source Module<'source>> {
|
||||
self.module
|
||||
.ok_or_else(|| anyhow!("internal error: current module not set"))
|
||||
@@ -1435,6 +1477,7 @@ impl<'source> Interpreter<'source> {
|
||||
let module_path =
|
||||
Self::get_path_string(&self.current_module()?.package.refr, Some("data"))?;
|
||||
let path = module_path + "." + name;
|
||||
|
||||
self.ensure_rule_evaluated(path)?;
|
||||
|
||||
let mut path: Vec<&str> =
|
||||
@@ -1933,62 +1976,65 @@ impl<'source> Interpreter<'source> {
|
||||
head: rule_head,
|
||||
bodies: rule_body,
|
||||
} => {
|
||||
if matches!(rule_head, RuleHead::Func { .. }) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (ctx, mut path) = self.make_rule_context(rule_head)?;
|
||||
let special_set = matches!((ctx.output_expr, &ctx.value), (None, Value::Set(_)));
|
||||
let value = match self.eval_rule_bodies(ctx, span, rule_body)? {
|
||||
Value::Set(_) if special_set => {
|
||||
let entry = path[path.len() - 1].text();
|
||||
let mut s = BTreeSet::new();
|
||||
s.insert(Value::String(entry.to_owned()));
|
||||
path = path[0..path.len() - 1].to_vec();
|
||||
Value::from_set(s)
|
||||
if !matches!(rule_head, RuleHead::Func { .. }) {
|
||||
let (ctx, mut path) = self.make_rule_context(rule_head)?;
|
||||
let special_set =
|
||||
matches!((ctx.output_expr, &ctx.value), (None, Value::Set(_)));
|
||||
let value = match self.eval_rule_bodies(ctx, span, rule_body)? {
|
||||
Value::Set(_) if special_set => {
|
||||
let entry = path[path.len() - 1].text();
|
||||
let mut s = BTreeSet::new();
|
||||
s.insert(Value::String(entry.to_owned()));
|
||||
path = path[0..path.len() - 1].to_vec();
|
||||
Value::from_set(s)
|
||||
}
|
||||
v => v,
|
||||
};
|
||||
if value != Value::Undefined {
|
||||
let paths: Vec<&str> = path.iter().map(|s| s.text()).collect();
|
||||
let vref = Self::make_or_get_value_mut(&mut self.data, &paths[..])?;
|
||||
Self::merge_value(span, vref, value)?;
|
||||
}
|
||||
v => v,
|
||||
};
|
||||
|
||||
if value != Value::Undefined {
|
||||
let paths: Vec<&str> = path.iter().map(|s| s.text()).collect();
|
||||
let vref = Self::make_or_get_value_mut(&mut self.data, &paths[..])?;
|
||||
Self::merge_value(span, vref, value)?;
|
||||
self.processed.insert(rule);
|
||||
}
|
||||
}
|
||||
_ => bail!("internal error: unexpected"),
|
||||
}
|
||||
self.set_current_module(prev_module)?;
|
||||
self.processed.insert(rule);
|
||||
match self.active_rules.pop() {
|
||||
Some(r) if r == rule => Ok(()),
|
||||
_ => bail!("internal error: current rule not active"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eval(
|
||||
pub fn eval_rule_with_input(
|
||||
&mut self,
|
||||
data: &Option<Value>,
|
||||
module: &'source Module<'source>,
|
||||
rule: &'source Rule<'source>,
|
||||
input: &Option<Value>,
|
||||
enable_tracing: bool,
|
||||
schedule: Option<&'source Schedule<'source>>,
|
||||
) -> Result<Value> {
|
||||
self.schedule = schedule;
|
||||
self.traces = match enable_tracing {
|
||||
true => Some(vec![]),
|
||||
false => None,
|
||||
};
|
||||
self.checks_for_eval(input, enable_tracing)?;
|
||||
self.clean_internal_evaluation_state();
|
||||
|
||||
self.eval_rule(module, rule)?;
|
||||
|
||||
Ok(self.data.clone())
|
||||
}
|
||||
|
||||
pub fn prepare_for_eval(
|
||||
&mut self,
|
||||
schedule: Option<&'source Schedule<'source>>,
|
||||
data: &Option<Value>,
|
||||
) -> Result<()> {
|
||||
self.schedule = schedule;
|
||||
self.builtins_cache.clear();
|
||||
|
||||
if let Some(input) = input {
|
||||
self.input = input.clone();
|
||||
|
||||
info!("input: {:#?}", self.input);
|
||||
}
|
||||
if let Some(data) = data {
|
||||
self.data = data.clone();
|
||||
}
|
||||
|
||||
// Ensure that each module has an empty object
|
||||
for m in &self.modules {
|
||||
let path = Parser::get_path_ref_components(&m.package.refr)?;
|
||||
@@ -2003,6 +2049,39 @@ impl<'source> Interpreter<'source> {
|
||||
self.update_function_table()?;
|
||||
self.gather_rules()?;
|
||||
|
||||
self.init_data = self.data.clone();
|
||||
self.prepared = true;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn eval_module(
|
||||
&mut self,
|
||||
module: &'source Module<'source>,
|
||||
input: &Option<Value>,
|
||||
enable_tracing: bool,
|
||||
) -> Result<Value> {
|
||||
self.checks_for_eval(input, enable_tracing)?;
|
||||
self.clean_internal_evaluation_state();
|
||||
|
||||
for rule in &module.policy {
|
||||
self.eval_rule(module, rule)?;
|
||||
}
|
||||
|
||||
// Defer the evaluation of the default rules to here
|
||||
let prev_module = self.set_current_module(Some(module))?;
|
||||
for rule in &module.policy {
|
||||
self.eval_default_rule(rule)?;
|
||||
}
|
||||
self.set_current_module(prev_module)?;
|
||||
|
||||
Ok(self.data.clone())
|
||||
}
|
||||
|
||||
pub fn eval_modules(&mut self, input: &Option<Value>, enable_tracing: bool) -> Result<Value> {
|
||||
self.checks_for_eval(input, enable_tracing)?;
|
||||
self.clean_internal_evaluation_state();
|
||||
|
||||
for module in self.modules.clone() {
|
||||
for rule in &module.policy {
|
||||
self.eval_rule(module, rule)?;
|
||||
@@ -2021,6 +2100,17 @@ impl<'source> Interpreter<'source> {
|
||||
Ok(self.data.clone())
|
||||
}
|
||||
|
||||
pub fn eval(
|
||||
&mut self,
|
||||
data: &Option<Value>,
|
||||
input: &Option<Value>,
|
||||
enable_tracing: bool,
|
||||
schedule: Option<&'source Schedule<'source>>,
|
||||
) -> Result<Value> {
|
||||
self.prepare_for_eval(schedule, data)?;
|
||||
self.eval_modules(input, enable_tracing)
|
||||
}
|
||||
|
||||
pub fn eval_query_snippet(
|
||||
&mut self,
|
||||
snippet: &'source Expr<'source>,
|
||||
|
||||
@@ -29,14 +29,14 @@ fn basic() -> Result<()> {
|
||||
}
|
||||
"#;
|
||||
|
||||
let expected = Value::from_json_str(
|
||||
let expected = vec![Value::from_json_str(
|
||||
r#" {
|
||||
"add" : true,
|
||||
"sub" : true,
|
||||
"mul" : true,
|
||||
"div" : true
|
||||
}"#,
|
||||
)?;
|
||||
)?];
|
||||
|
||||
assert_eq!(
|
||||
eval_file(&[rego.to_owned()], None, None, "data.test", false)?,
|
||||
|
||||
@@ -32,7 +32,7 @@ fn basic_array() -> Result<()> {
|
||||
array_compr_7 = [ 1 | [1, 2, 3][_]; [1, 2][_] >= 2 ]
|
||||
"#;
|
||||
|
||||
let expected = Value::from_json_str(
|
||||
let expected = vec![Value::from_json_str(
|
||||
r#" {
|
||||
"array": [1, 2, 3],
|
||||
"array_compr_0": [1],
|
||||
@@ -44,7 +44,7 @@ fn basic_array() -> Result<()> {
|
||||
"array_compr_6": [1, 1, 1, 1, 1, 1],
|
||||
"array_compr_7": [1, 1, 1]
|
||||
}"#,
|
||||
)?;
|
||||
)?];
|
||||
|
||||
assert_match(
|
||||
eval_file(&[rego.to_owned()], None, None, "data.test", false)?,
|
||||
@@ -81,7 +81,7 @@ fn basic_set() -> Result<()> {
|
||||
set_compr_7 = { a | a = [1, 2, 3][_]; [1, 2][_] >= 2 }
|
||||
"#;
|
||||
|
||||
let expected = Value::from_json_str(
|
||||
let expected = vec![Value::from_json_str(
|
||||
r#" {
|
||||
"set": {
|
||||
"set!": [1, "string", [2, 3, 4], 567, false]
|
||||
@@ -113,7 +113,7 @@ fn basic_set() -> Result<()> {
|
||||
"set!": [1, 2, 3]
|
||||
}
|
||||
}"#,
|
||||
)?;
|
||||
)?];
|
||||
|
||||
assert_match(
|
||||
eval_file(&[rego.to_owned()], None, None, "data.test", false)?,
|
||||
|
||||
@@ -64,4 +64,3 @@ cases:
|
||||
x = { k:v | k = ["Hello", "world", 1][_]; v = [1, 2][_] }
|
||||
query: data.test
|
||||
error: "value for key `\"Hello\"` generated multiple times: `1` and `2`"
|
||||
want_result:
|
||||
|
||||
@@ -99,7 +99,7 @@ fn basic() -> Result<()> {
|
||||
}
|
||||
"#;
|
||||
|
||||
let expected = Value::from_json_str(
|
||||
let expected = vec![Value::from_json_str(
|
||||
r#" {
|
||||
"array": [1, 2, 3],
|
||||
"in_array_key_value": true,
|
||||
@@ -117,7 +117,7 @@ fn basic() -> Result<()> {
|
||||
"in_set_value": true,
|
||||
"some_decl_set_value": true
|
||||
}"#,
|
||||
)?;
|
||||
)?];
|
||||
|
||||
assert_match(
|
||||
eval_file(&[rego.to_owned()], None, None, "data.test", false)?,
|
||||
|
||||
48
tests/interpreter/cases/input/mod.rs
Normal file
48
tests/interpreter/cases/input/mod.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use crate::interpreter::*;
|
||||
use anyhow::Result;
|
||||
|
||||
#[test]
|
||||
fn basic() -> Result<()> {
|
||||
let rego = r#"
|
||||
package test
|
||||
|
||||
x[a] {
|
||||
a = y
|
||||
}
|
||||
|
||||
y[a] {
|
||||
a = input.x + 5
|
||||
}
|
||||
"#;
|
||||
|
||||
let input = ValueOrVec::Many(vec![
|
||||
Value::from_json_str(r#"{"x": 1}"#)?,
|
||||
Value::from_json_str(r#"{"x": 6}"#)?,
|
||||
]);
|
||||
|
||||
let expected = vec![
|
||||
Value::from_json_str(
|
||||
r#" {
|
||||
"y": {"set!": [6]},
|
||||
"x": {"set!": [{"set!":[6]}]}
|
||||
}"#,
|
||||
)?,
|
||||
Value::from_json_str(
|
||||
r#" {
|
||||
"y": {"set!": [11]},
|
||||
"x": {"set!": [{"set!":[11]}]}
|
||||
}"#,
|
||||
)?,
|
||||
];
|
||||
|
||||
assert_match(
|
||||
eval_file_first_rule(&[rego.to_owned()], None, Some(input), "data.test", false)?,
|
||||
expected,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
33
tests/interpreter/cases/input/multiple.yaml
Normal file
33
tests/interpreter/cases/input/multiple.yaml
Normal file
@@ -0,0 +1,33 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
cases:
|
||||
- note: input-multiple-1
|
||||
data: {}
|
||||
input:
|
||||
many!:
|
||||
- { x: 1 }
|
||||
- { x: 5 }
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
x[a] {
|
||||
a = y
|
||||
}
|
||||
|
||||
y[a] {
|
||||
a = input.x + 5
|
||||
}
|
||||
|
||||
query: data.test
|
||||
want_result:
|
||||
many!:
|
||||
- y:
|
||||
set!: [6]
|
||||
x:
|
||||
set!: [ set!: [6] ]
|
||||
- y:
|
||||
set!: [10]
|
||||
x:
|
||||
set!: [ set!: [10] ]
|
||||
27
tests/interpreter/cases/input/simple.yaml
Normal file
27
tests/interpreter/cases/input/simple.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
cases:
|
||||
- note: input-basic-1
|
||||
data: {}
|
||||
input: {
|
||||
x: 1
|
||||
}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
x[a] {
|
||||
a = y
|
||||
}
|
||||
|
||||
y[a] {
|
||||
a = input.x + 5
|
||||
}
|
||||
|
||||
query: data.test
|
||||
want_result:
|
||||
y:
|
||||
set!: [6]
|
||||
x:
|
||||
set!: [ set!: [6] ]
|
||||
@@ -5,4 +5,5 @@ mod arithmetic;
|
||||
mod builtins;
|
||||
mod compr;
|
||||
mod r#in;
|
||||
mod input;
|
||||
mod variables;
|
||||
|
||||
@@ -31,7 +31,7 @@ fn basic() -> Result<()> {
|
||||
set = {1, 2, 3}
|
||||
"#;
|
||||
|
||||
let expected = Value::from_json_str(
|
||||
let expected = vec![Value::from_json_str(
|
||||
r#" {
|
||||
"array": [1, 2, 3],
|
||||
"nested_array": [1, [2, 3, 4], 5, 6],
|
||||
@@ -44,7 +44,7 @@ fn basic() -> Result<()> {
|
||||
"local_0": 10,
|
||||
"local_1": "test_local"
|
||||
}"#,
|
||||
)?;
|
||||
)?];
|
||||
|
||||
assert_match(
|
||||
eval_file(&[rego.to_owned()], None, None, "data.test", false)?,
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::env;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use regorus::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
|
||||
use test_generator::test_resources;
|
||||
//use walkdir::WalkDir;
|
||||
|
||||
@@ -154,28 +154,52 @@ fn match_values(computed: &Value, expected: &Value) -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn assert_match(computed: Value, expected: Value) {
|
||||
let expected = match process_value(&expected) {
|
||||
Ok(e) => e,
|
||||
_ => panic!("unable to process value :\n {expected:?}"),
|
||||
};
|
||||
match match_values(&computed, &expected) {
|
||||
Ok(()) => (),
|
||||
Err(e) => panic!("{}", e),
|
||||
pub fn assert_match(computed_results: Vec<Value>, expected_results: Vec<Value>) {
|
||||
if computed_results.len() != expected_results.len() {
|
||||
panic!(
|
||||
"the number of computed results ({}) and expected results ({}) is not equal",
|
||||
computed_results.len(),
|
||||
expected_results.len()
|
||||
);
|
||||
}
|
||||
|
||||
for (n, expected_result) in expected_results.into_iter().enumerate() {
|
||||
let expected = match process_value(&expected_result) {
|
||||
Ok(e) => e,
|
||||
_ => panic!("unable to process value :\n {expected_result:?}"),
|
||||
};
|
||||
|
||||
if let Some(computed_result) = computed_results.get(n) {
|
||||
match match_values(computed_result, &expected) {
|
||||
Ok(()) => (),
|
||||
Err(e) => panic!("{}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eval_file(
|
||||
pub fn eval_file_first_rule(
|
||||
regos: &[String],
|
||||
data: Option<Value>,
|
||||
input: Option<Value>,
|
||||
data_opt: Option<Value>,
|
||||
input_opt: Option<ValueOrVec>,
|
||||
query: &str,
|
||||
enable_tracing: bool,
|
||||
) -> Result<Value> {
|
||||
) -> Result<Vec<Value>> {
|
||||
let mut results = vec![];
|
||||
let mut files = vec![];
|
||||
let mut sources = vec![];
|
||||
let mut modules = vec![];
|
||||
let mut modules_ref = vec![];
|
||||
|
||||
// the query is parsed for later
|
||||
let source = Source {
|
||||
file: "<query.rego>",
|
||||
contents: query,
|
||||
lines: query.split('\n').collect(),
|
||||
};
|
||||
let mut parser = Parser::new(&source)?;
|
||||
let expr = parser.parse_membership_expr()?;
|
||||
|
||||
for (idx, _) in regos.iter().enumerate() {
|
||||
files.push(format!("rego_{idx}"));
|
||||
}
|
||||
@@ -201,11 +225,53 @@ pub fn eval_file(
|
||||
let analyzer = Analyzer::new();
|
||||
let schedule = analyzer.analyze(&modules)?;
|
||||
|
||||
// First eval the modules.
|
||||
let mut interpreter = interpreter::Interpreter::new(modules_ref)?;
|
||||
interpreter.eval(&data, &input, enable_tracing, Some(&schedule))?;
|
||||
if let Some(input) = input_opt {
|
||||
// if inputs are defined then first the evaluation if prepared
|
||||
interpreter.prepare_for_eval(Some(&schedule), &data_opt)?;
|
||||
|
||||
// Now eval the query.
|
||||
// then 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),
|
||||
}
|
||||
|
||||
for input in inputs {
|
||||
if let Some(module) = &modules.get(0) {
|
||||
if let Some(rule) = &module.policy.get(0) {
|
||||
interpreter.eval_rule_with_input(module, rule, &Some(input), enable_tracing)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Now eval the query.
|
||||
results.push(interpreter.eval_query_snippet(&expr, enable_tracing)?);
|
||||
}
|
||||
} else {
|
||||
// it no input is defined then one evaluation of all modules is performed
|
||||
interpreter.eval(&data_opt, &None, enable_tracing, Some(&schedule))?;
|
||||
|
||||
// Now eval the query.
|
||||
results.push(interpreter.eval_query_snippet(&expr, enable_tracing)?);
|
||||
}
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
pub fn eval_file(
|
||||
regos: &[String],
|
||||
data_opt: Option<Value>,
|
||||
input_opt: Option<ValueOrVec>,
|
||||
query: &str,
|
||||
enable_tracing: bool,
|
||||
) -> Result<Vec<Value>> {
|
||||
let mut results = vec![];
|
||||
let mut files = vec![];
|
||||
let mut sources = vec![];
|
||||
let mut modules = vec![];
|
||||
let mut modules_ref = vec![];
|
||||
|
||||
// the query is parsed for later
|
||||
let source = Source {
|
||||
file: "<query.rego>",
|
||||
contents: query,
|
||||
@@ -213,7 +279,59 @@ pub fn eval_file(
|
||||
};
|
||||
let mut parser = Parser::new(&source)?;
|
||||
let expr = parser.parse_membership_expr()?;
|
||||
interpreter.eval_query_snippet(&expr, enable_tracing)
|
||||
|
||||
for (idx, _) in regos.iter().enumerate() {
|
||||
files.push(format!("rego_{idx}"));
|
||||
}
|
||||
|
||||
for (idx, file) in files.iter().enumerate() {
|
||||
let contents = regos[idx].as_str();
|
||||
sources.push(Source {
|
||||
file,
|
||||
contents,
|
||||
lines: contents.split('\n').collect(),
|
||||
});
|
||||
}
|
||||
|
||||
for source in &sources {
|
||||
let mut parser = Parser::new(source)?;
|
||||
modules.push(parser.parse()?);
|
||||
}
|
||||
|
||||
for m in &modules {
|
||||
modules_ref.push(m);
|
||||
}
|
||||
|
||||
let analyzer = Analyzer::new();
|
||||
let schedule = analyzer.analyze(&modules)?;
|
||||
|
||||
let mut interpreter = interpreter::Interpreter::new(modules_ref)?;
|
||||
if let Some(input) = input_opt {
|
||||
// if inputs are defined then first the evaluation if prepared
|
||||
interpreter.prepare_for_eval(Some(&schedule), &data_opt)?;
|
||||
|
||||
// then 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),
|
||||
}
|
||||
|
||||
for input in inputs {
|
||||
interpreter.eval_modules(&Some(input), enable_tracing)?;
|
||||
|
||||
// Now eval the query.
|
||||
results.push(interpreter.eval_query_snippet(&expr, enable_tracing)?);
|
||||
}
|
||||
} else {
|
||||
// it no input is defined then one evaluation of all modules is performed
|
||||
interpreter.eval(&data_opt, &None, enable_tracing, Some(&schedule))?;
|
||||
|
||||
// Now eval the query.
|
||||
results.push(interpreter.eval_query_snippet(&expr, enable_tracing)?);
|
||||
}
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -256,20 +374,57 @@ fn one_file() -> Result<()> {
|
||||
}
|
||||
|
||||
let mut interpreter = interpreter::Interpreter::new(modules_ref)?;
|
||||
let results = interpreter.eval(&None, &input, true, Some(&schedule))?;
|
||||
interpreter.prepare_for_eval(Some(&schedule), &None)?;
|
||||
let results = interpreter.eval_modules(&input, true)?;
|
||||
println!("eval results:\n{}", serde_json::to_string_pretty(&results)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum ValueOrVec {
|
||||
Single(Value),
|
||||
Many(Vec<Value>),
|
||||
}
|
||||
|
||||
impl Serialize for ValueOrVec {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match self {
|
||||
ValueOrVec::Single(value) => value.serialize(serializer),
|
||||
ValueOrVec::Many(v) => {
|
||||
let mut map = serializer.serialize_map(Some(1))?;
|
||||
map.serialize_entry("many!", v)?;
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ValueOrVec {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let value = Value::deserialize(deserializer)?;
|
||||
|
||||
match &value["many!"] {
|
||||
Value::Array(arr) => Ok(ValueOrVec::Many(arr.to_vec())),
|
||||
_ => Ok(ValueOrVec::Single(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
struct TestCase {
|
||||
data: Value,
|
||||
input: Option<Value>,
|
||||
input: Option<ValueOrVec>,
|
||||
modules: Vec<String>,
|
||||
note: String,
|
||||
query: String,
|
||||
sort_bindings: Option<bool>,
|
||||
want_result: Option<Value>,
|
||||
want_result: Option<ValueOrVec>,
|
||||
skip: Option<bool>,
|
||||
error: Option<String>,
|
||||
traces: Option<bool>,
|
||||
@@ -285,6 +440,7 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
||||
let test: YamlTest = serde_yaml::from_str(&yaml_str)?;
|
||||
|
||||
println!("running {file}");
|
||||
|
||||
for case in test.cases {
|
||||
print!("case {} ", case.note);
|
||||
if case.skip == Some(true) {
|
||||
@@ -298,7 +454,7 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
||||
}
|
||||
|
||||
let enable_tracing = case.traces.is_some() && case.traces.unwrap();
|
||||
// First eval the modules.
|
||||
|
||||
match eval_file(
|
||||
&case.modules,
|
||||
Some(case.data),
|
||||
@@ -307,7 +463,17 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
||||
enable_tracing,
|
||||
) {
|
||||
Ok(results) => match case.want_result {
|
||||
Some(want_result) => assert_match(results, want_result),
|
||||
Some(want_result) => {
|
||||
let mut expected_results = vec![];
|
||||
match want_result {
|
||||
ValueOrVec::Single(single_result) => expected_results.push(single_result),
|
||||
ValueOrVec::Many(mut many_result) => {
|
||||
expected_results.append(&mut many_result)
|
||||
}
|
||||
}
|
||||
|
||||
assert_match(results, expected_results);
|
||||
}
|
||||
_ => panic!("eval succeeded and did not produce any errors"),
|
||||
},
|
||||
Err(actual) => match &case.error {
|
||||
|
||||
Reference in New Issue
Block a user