mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
Preserve false in single-expression queries (#145)
Note: 1 = 2 is different from 1 == 2 See issue for details fixes #144 Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
committed by
GitHub
parent
7d32bd9377
commit
8d282f1ffd
@@ -233,7 +233,7 @@ impl Engine {
|
||||
/// // Load input and make query.
|
||||
/// engine.set_input(Value::new_object());
|
||||
/// let results = engine.eval_query("data.framework.mount_overlay.allowed".to_string(), false)?;
|
||||
/// assert!(results.result.is_empty());
|
||||
/// assert_eq!(results.result[0].expressions[0].value, Value::from(false));
|
||||
///
|
||||
/// // Evaluate query with different inputs.
|
||||
/// engine.set_input(Value::from_json_file("tests/aci/input.json")?);
|
||||
|
||||
@@ -661,7 +661,13 @@ impl Interpreter {
|
||||
.map(Value::Bool);
|
||||
}
|
||||
// Treat the assignment as comparison if neither lhs nor rhs is a variable
|
||||
_ => return self.eval_bool_expr(&BoolOp::Eq, lhs, rhs),
|
||||
_ => {
|
||||
let r = self.eval_bool_expr(&BoolOp::Eq, lhs, rhs)?;
|
||||
if r == Value::Bool(false) {
|
||||
return Ok(Value::Undefined);
|
||||
}
|
||||
return Ok(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
AssignOp::ColEq => {
|
||||
@@ -1702,11 +1708,12 @@ impl Interpreter {
|
||||
.insert(Value::String(name.to_string().into()), value.clone());
|
||||
}
|
||||
}
|
||||
if result
|
||||
.expressions
|
||||
.iter()
|
||||
.all(|v| v.value != Value::Undefined && v.value != Value::Bool(false))
|
||||
&& !result.expressions.is_empty()
|
||||
if result.expressions.len() == 1 // Single expression query
|
||||
|| result // Multi expression query where no value is false
|
||||
.expressions
|
||||
.iter()
|
||||
.all(|v| v.value != Value::Undefined && v.value != Value::Bool(false))
|
||||
&& !result.expressions.is_empty()
|
||||
{
|
||||
ctx.results.result.push(result);
|
||||
}
|
||||
@@ -1821,7 +1828,9 @@ impl Interpreter {
|
||||
.insert(Value::String(name.to_string().into()), value.clone());
|
||||
}
|
||||
}
|
||||
if result
|
||||
|
||||
if result.expressions.len() == 1 // Single expression query
|
||||
|| result // Multi expression query where no value is false
|
||||
.expressions
|
||||
.iter()
|
||||
.all(|v| v.value != Value::Undefined && v.value != Value::Bool(false))
|
||||
@@ -3242,7 +3251,8 @@ impl Interpreter {
|
||||
|
||||
if let Some(r) = results.result.last() {
|
||||
if matches!(&r.bindings, Value::Object(obj) if obj.is_empty())
|
||||
&& r.expressions.iter().any(|e| e.value == Value::Bool(false))
|
||||
&& (r.expressions.len() > 1
|
||||
&& r.expressions.iter().any(|e| e.value == Value::Bool(false)))
|
||||
{
|
||||
results = QueryResults::default();
|
||||
}
|
||||
|
||||
42
src/lib.rs
42
src/lib.rs
@@ -186,17 +186,33 @@ impl Default for QueryResult {
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// // Create engine and evaluate "true; true; false".
|
||||
/// // Create engine and evaluate "1 + 1".
|
||||
/// let results = Engine::new().eval_query("1 + 1".to_string(), false)?;
|
||||
///
|
||||
/// assert!(results.result.len() == 1);
|
||||
/// assert_eq!(results.result.len(), 1);
|
||||
/// assert_eq!(results.result[0].expressions[0].value, Value::from(2u64));
|
||||
/// assert_eq!(results.result[0].expressions[0].text.as_ref(), "1 + 1");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// If any expression evaluates to false, then no results are produced.
|
||||
/// If a query contains only one expression, and even if the expression evaluates
|
||||
/// to false, the value will be returned.
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// // Create engine and evaluate "1 > 2" which is false.
|
||||
/// let results = Engine::new().eval_query("1 > 2".to_string(), false)?;
|
||||
///
|
||||
/// assert_eq!(results.result.len(), 1);
|
||||
/// assert_eq!(results.result[0].expressions[0].value, Value::from(false));
|
||||
/// assert_eq!(results.result[0].expressions[0].text.as_ref(), "1 > 2");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// In a query containing multiple expressions, if any expression evaluates to false,
|
||||
/// then no results are produced.
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
@@ -208,6 +224,26 @@ impl Default for QueryResult {
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Note that `=` is different from `==`. The former evaluates to undefined if the LHS and RHS
|
||||
/// are not equal. The latter evaluates to either true or false.
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// // Create engine and evaluate "1 = 2" which is undefined and produces no resutl.
|
||||
/// let results = Engine::new().eval_query("1 = 2".to_string(), false)?;
|
||||
///
|
||||
/// assert_eq!(results.result.len(), 0);
|
||||
///
|
||||
/// // Create engine and evaluate "1 == 2" which evaluates to false.
|
||||
/// let results = Engine::new().eval_query("1 == 2".to_string(), false)?;
|
||||
///
|
||||
/// assert_eq!(results.result.len(), 1);
|
||||
/// assert_eq!(results.result[0].expressions[0].value, Value::from(false));
|
||||
/// assert_eq!(results.result[0].expressions[0].text.as_ref(), "1 == 2");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Queries containing loops produce multiple results.
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
|
||||
@@ -234,6 +234,7 @@ struct TestCase {
|
||||
query: String,
|
||||
sort_bindings: Option<bool>,
|
||||
want_result: Option<ValueOrVec>,
|
||||
no_result: Option<bool>,
|
||||
skip: Option<bool>,
|
||||
error: Option<String>,
|
||||
traces: Option<bool>,
|
||||
@@ -267,7 +268,10 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
||||
|
||||
match (&case.want_result, &case.error) {
|
||||
(Some(_), None) | (None, Some(_)) => (),
|
||||
_ => panic!("either want_result or error must be specified in test case."),
|
||||
_ if case.no_result != Some(true) => {
|
||||
panic!("either want_result, error or no_result must be specified in test case.")
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let enable_tracing = case.traces.is_some() && case.traces.unwrap();
|
||||
@@ -292,6 +296,7 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
||||
|
||||
check_output(&results, &expected_results)?;
|
||||
}
|
||||
_ if case.no_result == Some(true) => (),
|
||||
_ => bail!("eval succeeded and did not produce any errors"),
|
||||
},
|
||||
Err(actual) => match &case.error {
|
||||
|
||||
26
tests/interpreter/cases/query/tests.yaml
Normal file
26
tests/interpreter/cases/query/tests.yaml
Normal file
@@ -0,0 +1,26 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
cases:
|
||||
- note: single-expression query producing false
|
||||
data: {}
|
||||
modules: []
|
||||
query: 1 == 2
|
||||
want_result: false
|
||||
|
||||
- note: single-expression query producing no results (due to undefined)
|
||||
data: {}
|
||||
modules: []
|
||||
query: 1 = 2
|
||||
no_result: true
|
||||
|
||||
- note: multi-expression query in which one expression is false (1)
|
||||
data: {}
|
||||
modules: []
|
||||
query: "1 == 1; 1 == 2"
|
||||
no_result: true
|
||||
|
||||
- note: multi-expression query in which one expression is false (2)
|
||||
data: {}
|
||||
modules: []
|
||||
query: "1 == 2; 1 == 1"
|
||||
no_result: true
|
||||
Reference in New Issue
Block a user