use super::exprs::*;
use super::*;
use crate::rules::eval::operators::Comparator;
use crate::rules::eval_context::{block_scope, resolve_function, ValueScope};
use crate::rules::path_value::compare_eq;
use std::collections::HashMap;
mod operators;
fn exists_operation(value: &QueryResult) -> Result<bool> {
Ok(match value {
QueryResult::Resolved(_) | QueryResult::Literal(_) => true,
QueryResult::UnResolved(_) => false,
})
}
fn element_empty_operation(value: &QueryResult) -> Result<bool> {
let result = match value {
QueryResult::Literal(value) | QueryResult::Resolved(value) => match &**value {
PathAwareValue::List((_, list)) => list.is_empty(),
PathAwareValue::Map((_, map)) => map.is_empty(),
PathAwareValue::String((_, string)) => string.is_empty(),
PathAwareValue::Bool((_, boolean)) => (*boolean).to_string().is_empty(),
_ => {
return Err(Error::IncompatibleError(format!(
"Attempting EMPTY operation on type {} that does not support it at {}",
value.type_info(),
value.self_path()
)))
}
},
QueryResult::UnResolved(_) => true,
};
Ok(result)
}
macro_rules! is_type_fn {
($name: ident, $type_: pat) => {
fn $name(value: &QueryResult) -> Result<bool> {
Ok(match value {
QueryResult::Literal(resolved) | QueryResult::Resolved(resolved) => {
match **resolved {
$type_ => true,
_ => false,
}
}
QueryResult::UnResolved(_) => false,
})
}
};
}
is_type_fn!(is_string_operation, PathAwareValue::String(_));
is_type_fn!(is_list_operation, PathAwareValue::List(_));
is_type_fn!(is_struct_operation, PathAwareValue::Map(_));
is_type_fn!(is_int_operation, PathAwareValue::Int(_));
is_type_fn!(is_float_operation, PathAwareValue::Float(_));
is_type_fn!(is_bool_operation, PathAwareValue::Bool(_));
#[cfg(test)]
is_type_fn!(is_char_range_operation, PathAwareValue::RangeChar(_));
#[cfg(test)]
is_type_fn!(is_int_range_operation, PathAwareValue::RangeInt(_));
#[cfg(test)]
is_type_fn!(is_float_range_operation, PathAwareValue::RangeFloat(_));
is_type_fn!(is_null_operation, PathAwareValue::Null(_));
fn not_operation<O>(operation: O) -> impl Fn(&QueryResult) -> Result<bool>
where
O: Fn(&QueryResult) -> Result<bool>,
{
move |value: &QueryResult| {
Ok(match operation(value)? {
true => false,
false => true,
})
}
}
fn inverse_operation<O>(operation: O, inverse: bool) -> impl Fn(&QueryResult) -> Result<bool>
where
O: Fn(&QueryResult) -> Result<bool>,
{
move |value: &QueryResult| {
Ok(match inverse {
true => !operation(value)?,
false => operation(value)?,
})
}
}
#[allow(clippy::type_complexity)]
fn record_unary_clause<'eval, 'value, 'loc: 'value, O>(
operation: O,
cmp: (CmpOperator, bool),
context: String,
custom_message: Option<String>,
eval_context: &'eval mut dyn EvalContext<'value, 'loc>,
) -> Box<dyn FnMut(&QueryResult) -> Result<bool> + 'eval>
where
O: Fn(&QueryResult) -> Result<bool> + 'eval,
{
Box::new(move |value: &QueryResult| {
eval_context.start_record(&context)?;
let mut check = ValueCheck {
custom_message: custom_message.clone(),
message: None,
status: Status::PASS,
from: value.clone(),
};
match operation(value) {
Ok(result) => {
if !result {
check.status = Status::FAIL;
eval_context.end_record(
&context,
RecordType::ClauseValueCheck(ClauseCheck::Unary(UnaryValueCheck {
value: check,
comparison: cmp,
})),
)?;
} else {
eval_context
.end_record(&context, RecordType::ClauseValueCheck(ClauseCheck::Success))?;
}
Ok(result)
}
Err(e) => {
check.status = Status::FAIL;
check.message = Some(format!("{}", e));
eval_context.end_record(
&context,
RecordType::ClauseValueCheck(ClauseCheck::Unary(UnaryValueCheck {
value: check,
comparison: cmp,
})),
)?;
Err(e)
}
}
})
}
macro_rules! box_create_func {
($name: ident, $not: expr, $inverse: expr, $cmp: ident, $eval: ident, $cxt: ident, $msg: ident) => {{
{
match $not {
true => record_unary_clause(
inverse_operation(not_operation($name), $inverse),
$cmp,
$cxt,
$msg,
$eval,
),
false => {
record_unary_clause(inverse_operation($name, $inverse), $cmp, $cxt, $msg, $eval)
}
}
}
}};
}
pub(super) enum EvaluationResult {
EmptyQueryResult(Status),
QueryValueResult(Vec<(QueryResult, Status)>),
}
#[allow(clippy::type_complexity)]
fn unary_operation<'r, 'l: 'r, 'loc: 'l>(
lhs_query: &'l [QueryPart<'loc>],
cmp: (CmpOperator, bool),
inverse: bool,
context: String,
custom_message: Option<String>,
eval_context: &'r mut dyn EvalContext<'l, 'loc>,
) -> Result<EvaluationResult> {
let lhs = eval_context.query(lhs_query)?;
let empty_on_expr = match &lhs_query[lhs_query.len() - 1] {
QueryPart::Filter(_, _) | QueryPart::MapKeyFilter(_, _) => true,
rest => rest.is_variable() && lhs_query.len() == 1,
};
if empty_on_expr && cmp.0 == CmpOperator::Empty {
return Ok({
if !lhs.is_empty() {
let mut results = Vec::with_capacity(lhs.len());
for each in lhs {
eval_context.start_record(&context)?;
let (result, status) = match each {
QueryResult::Literal(res) | QueryResult::Resolved(res) => {
let status = if cmp.1 {
!res.is_null()
} else {
res.is_null()
};
(
QueryResult::Resolved(res),
match status {
true => Status::PASS, false => Status::FAIL, },
)
}
QueryResult::UnResolved(ur) => {
(
QueryResult::UnResolved(ur),
match cmp.1 {
true => Status::FAIL, false => Status::PASS, },
)
}
};
let status = if inverse {
match status {
Status::PASS => Status::FAIL,
Status::FAIL => Status::PASS,
_ => unreachable!(),
}
} else {
status
};
match status {
Status::PASS => {
eval_context.end_record(
&context,
RecordType::ClauseValueCheck(ClauseCheck::Success),
)?;
}
Status::FAIL => {
eval_context.end_record(
&context,
RecordType::ClauseValueCheck(ClauseCheck::Unary(UnaryValueCheck {
comparison: cmp,
value: ValueCheck {
status: Status::FAIL,
message: None,
custom_message: custom_message.clone(),
from: result.clone(),
},
})),
)?;
}
_ => unreachable!(),
}
results.push((result, status));
}
EvaluationResult::QueryValueResult(results)
} else {
EvaluationResult::EmptyQueryResult({
let result = !cmp.1;
let result = if inverse { !result } else { result };
match result {
true => {
eval_context.start_record(&context)?;
eval_context.end_record(
&context,
RecordType::ClauseValueCheck(ClauseCheck::Success),
)?;
Status::PASS
}
false => {
eval_context.start_record(&context)?;
eval_context.end_record(
&context,
RecordType::ClauseValueCheck(ClauseCheck::NoValueForEmptyCheck(
custom_message,
)),
)?;
Status::FAIL
}
}
})
}
});
}
if lhs.is_empty() {
return Ok(EvaluationResult::EmptyQueryResult(Status::SKIP));
}
use CmpOperator::*;
let mut operation: Box<dyn FnMut(&QueryResult) -> Result<bool>> = match cmp {
(CmpOperator::Exists, not_exists) => box_create_func!(
exists_operation,
not_exists,
inverse,
cmp,
eval_context,
context,
custom_message
),
(CmpOperator::Empty, not_empty) => box_create_func!(
element_empty_operation,
not_empty,
inverse,
cmp,
eval_context,
context,
custom_message
),
(CmpOperator::IsString, is_not_string) => box_create_func!(
is_string_operation,
is_not_string,
inverse,
cmp,
eval_context,
context,
custom_message
),
(CmpOperator::IsMap, is_not_map) => box_create_func!(
is_struct_operation,
is_not_map,
inverse,
cmp,
eval_context,
context,
custom_message
),
(CmpOperator::IsList, is_not_list) => box_create_func!(
is_list_operation,
is_not_list,
inverse,
cmp,
eval_context,
context,
custom_message
),
(CmpOperator::IsBool, is_not_bool) => box_create_func!(
is_bool_operation,
is_not_bool,
inverse,
cmp,
eval_context,
context,
custom_message
),
(CmpOperator::IsInt, is_not_int) => box_create_func!(
is_int_operation,
is_not_int,
inverse,
cmp,
eval_context,
context,
custom_message
),
(CmpOperator::IsNull, is_not_null) => box_create_func!(
is_null_operation,
is_not_null,
inverse,
cmp,
eval_context,
context,
custom_message
),
(CmpOperator::IsFloat, is_not_float) => box_create_func!(
is_float_operation,
is_not_float,
inverse,
cmp,
eval_context,
context,
custom_message
),
(Eq | Gt | Ge | Lt | Le | In, _) => unreachable!(),
};
let mut status = Vec::with_capacity(lhs.len());
for each in lhs {
match (*operation)(&each)? {
true => {
status.push((each, Status::PASS));
}
false => {
status.push((each, Status::FAIL));
}
}
}
Ok(EvaluationResult::QueryValueResult(status))
}
enum ComparisonResult {
Comparable(ComparisonWithRhs),
NotComparable(NotComparableWithRhs),
UnResolvedRhs(UnResolvedRhs),
}
struct LhsRhsPair {
lhs: Rc<PathAwareValue>,
rhs: Rc<PathAwareValue>,
}
struct ComparisonWithRhs {
outcome: bool,
pair: LhsRhsPair,
}
#[allow(dead_code)]
struct NotComparableWithRhs {
reason: String,
pair: LhsRhsPair,
}
struct UnResolvedRhs {
rhs: QueryResult,
lhs: Rc<PathAwareValue>,
}
fn each_lhs_compare<C>(
cmp: C,
lhs: Rc<PathAwareValue>,
rhs: &[QueryResult],
) -> Result<Vec<ComparisonResult>>
where
C: Fn(&PathAwareValue, &PathAwareValue) -> Result<bool>,
{
let mut statues = Vec::with_capacity(rhs.len());
for each_rhs in rhs {
match each_rhs {
QueryResult::Literal(each_rhs_resolved) | QueryResult::Resolved(each_rhs_resolved) => {
match cmp(&lhs, each_rhs_resolved) {
Ok(outcome) => {
statues.push(ComparisonResult::Comparable(ComparisonWithRhs {
outcome,
pair: LhsRhsPair {
lhs: Rc::clone(&lhs),
rhs: Rc::clone(each_rhs_resolved),
},
}));
}
Err(Error::NotComparable(reason)) => {
if lhs.is_list() {
if let PathAwareValue::List((_, inner)) = &*lhs {
for each in inner {
match cmp(each, each_rhs_resolved) {
Ok(outcome) => {
statues.push(ComparisonResult::Comparable(
ComparisonWithRhs {
outcome,
pair: LhsRhsPair {
lhs: Rc::new(each.clone()),
rhs: Rc::clone(each_rhs_resolved),
},
},
));
}
Err(Error::NotComparable(reason)) => {
statues.push(ComparisonResult::NotComparable(
NotComparableWithRhs {
reason,
pair: LhsRhsPair {
lhs: Rc::new(each.clone()),
rhs: Rc::clone(each_rhs_resolved),
},
},
));
}
Err(e) => return Err(e),
}
}
continue;
}
}
if lhs.is_scalar() {
if let QueryResult::Literal(_) = each_rhs {
if let PathAwareValue::List((_, rhs)) = &**each_rhs_resolved {
if rhs.len() == 1 {
let rhs_inner_single_element = &rhs[0];
match cmp(&lhs, rhs_inner_single_element) {
Ok(outcome) => {
statues.push(ComparisonResult::Comparable(
ComparisonWithRhs {
outcome,
pair: LhsRhsPair {
lhs: Rc::clone(&lhs),
rhs: Rc::new(
rhs_inner_single_element.clone(),
),
},
},
));
}
Err(Error::NotComparable(reason)) => {
statues.push(ComparisonResult::NotComparable(
NotComparableWithRhs {
reason,
pair: LhsRhsPair {
lhs: Rc::clone(&lhs),
rhs: Rc::new(
rhs_inner_single_element.clone(),
),
},
},
));
}
Err(e) => return Err(e),
}
continue;
}
}
}
}
statues.push(ComparisonResult::NotComparable(NotComparableWithRhs {
reason,
pair: LhsRhsPair {
lhs: Rc::clone(&lhs),
rhs: Rc::clone(each_rhs_resolved),
},
}));
}
Err(e) => return Err(e),
}
}
QueryResult::UnResolved(_ur) => {
statues.push(ComparisonResult::UnResolvedRhs(UnResolvedRhs {
rhs: each_rhs.clone(),
lhs: Rc::clone(&lhs),
}));
}
}
}
Ok(statues)
}
fn in_cmp(not_in: bool) -> impl Fn(&PathAwareValue, &PathAwareValue) -> Result<bool> {
move |lhs, rhs| match (lhs, rhs) {
(PathAwareValue::String((_, lhs_value)), PathAwareValue::String((_, rhs_value))) => {
let result = rhs_value.contains(lhs_value);
Ok(if not_in { !result } else { result })
}
(_, PathAwareValue::List((_, rhs_list))) => Ok({
let mut tracking = Vec::with_capacity(rhs_list.len());
for each_rhs in rhs_list {
tracking.push(compare_eq(lhs, each_rhs)?);
}
match tracking.iter().find(|s| **s) {
Some(_) => !not_in,
None => not_in,
}
}),
(_, _) => {
let result = compare_eq(lhs, rhs)?;
Ok(if not_in { !result } else { result })
}
}
}
fn report_value<'r, 'value: 'r, 'loc: 'value>(
each_res: &ComparisonResult,
cmp: (CmpOperator, bool),
context: String,
custom_message: Option<String>,
eval_context: &'r mut dyn EvalContext<'value, 'loc>,
) -> Result<(QueryResult, Status)> {
let (lhs_value, rhs_value, outcome, reason) = match each_res {
ComparisonResult::Comparable(ComparisonWithRhs {
outcome,
pair:
LhsRhsPair {
lhs: lhs_value,
rhs: rhs_value,
},
}) => (
QueryResult::Resolved(Rc::clone(lhs_value)),
Some(QueryResult::Resolved(Rc::clone(rhs_value))),
*outcome,
None,
),
ComparisonResult::NotComparable(NotComparableWithRhs {
pair:
LhsRhsPair {
rhs: rhs_value,
lhs: lhs_value,
},
..
}) => (
QueryResult::Resolved(Rc::clone(lhs_value)),
Some(QueryResult::Resolved(Rc::clone(rhs_value))),
false,
None,
),
ComparisonResult::UnResolvedRhs(UnResolvedRhs {
lhs: lhs_value,
rhs: rhs_query_result,
}) => (
QueryResult::Resolved(Rc::clone(lhs_value)),
Some(rhs_query_result.clone()),
false,
None,
), };
Ok(if outcome {
eval_context.start_record(&context)?;
eval_context.end_record(&context, RecordType::ClauseValueCheck(ClauseCheck::Success))?;
(lhs_value, Status::PASS)
} else {
eval_context.start_record(&context)?;
eval_context.end_record(
&context,
RecordType::ClauseValueCheck(ClauseCheck::Comparison(ComparisonClauseCheck {
from: lhs_value.clone(),
comparison: cmp,
to: rhs_value,
custom_message,
message: reason,
status: Status::FAIL,
})),
)?;
(lhs_value, Status::FAIL)
})
}
fn report_all_values<'r, 'value: 'r, 'loc: 'value>(
comparisons: Vec<ComparisonResult>,
cmp: (CmpOperator, bool),
context: String,
custom_message: Option<String>,
eval_context: &'r mut dyn EvalContext<'value, 'loc>,
) -> Result<Vec<(QueryResult, Status)>> {
let mut status = Vec::with_capacity(comparisons.len());
for each_res in comparisons {
status.push(report_value(
&each_res,
cmp,
context.clone(),
custom_message.clone(),
eval_context,
)?);
}
Ok(status)
}
fn report_at_least_one<'r, 'value: 'r, 'loc: 'value>(
rhs_comparisons: Vec<ComparisonResult>,
cmp: (CmpOperator, bool),
context: String,
custom_message: Option<String>,
eval_context: &'r mut dyn EvalContext<'value, 'loc>,
) -> Result<Vec<(QueryResult, Status)>> {
let mut statues = Vec::with_capacity(rhs_comparisons.len());
let mut by_lhs_value = HashMap::new();
for each in &rhs_comparisons {
match each {
ComparisonResult::Comparable(ComparisonWithRhs {
pair: LhsRhsPair { lhs, rhs },
..
}) => {
by_lhs_value
.entry(lhs)
.or_insert(vec![])
.push((each, QueryResult::Resolved(Rc::clone(rhs))));
}
ComparisonResult::NotComparable(NotComparableWithRhs {
pair: LhsRhsPair { lhs, rhs },
..
}) => {
by_lhs_value
.entry(lhs)
.or_insert(vec![])
.push((each, QueryResult::Resolved(Rc::clone(rhs))));
}
ComparisonResult::UnResolvedRhs(UnResolvedRhs { rhs, lhs }) => {
if let QueryResult::UnResolved(..) = rhs {
by_lhs_value
.entry(lhs)
.or_insert(vec![])
.push((each, rhs.clone()));
}
}
}
}
for (lhs, results) in by_lhs_value.iter() {
let found = results.iter().find(|(r, _rhs)| {
matches!(
r,
ComparisonResult::Comparable(ComparisonWithRhs { outcome: true, .. })
)
});
match found {
Some(_) => {
eval_context.start_record(&context)?;
eval_context
.end_record(&context, RecordType::ClauseValueCheck(ClauseCheck::Success))?;
statues.push((QueryResult::Resolved(Rc::clone(lhs)), Status::PASS))
}
None => {
eval_context.start_record(&context)?;
let to_collected = results
.iter()
.map(|(_, rhs)| rhs.clone())
.collect::<Vec<QueryResult>>();
eval_context.end_record(
&context,
RecordType::ClauseValueCheck(ClauseCheck::InComparison(InComparisonCheck {
from: QueryResult::Resolved(Rc::clone(lhs)),
to: to_collected,
message: None,
custom_message: custom_message.clone(),
status: Status::FAIL,
comparison: cmp,
})),
)?;
statues.push((QueryResult::Resolved(Rc::clone(lhs)), Status::FAIL))
}
}
}
Ok(statues)
}
fn not_compare<O>(cmp: O, invert: bool) -> impl Fn(&PathAwareValue, &PathAwareValue) -> Result<bool>
where
O: Fn(&PathAwareValue, &PathAwareValue) -> Result<bool>,
{
move |lhs, rhs| {
let r = cmp(lhs, rhs)?;
Ok(if invert { !r } else { r })
}
}
fn binary_operation<'value, 'loc: 'value>(
lhs_query: &'value [QueryPart<'loc>],
rhs: &[QueryResult],
cmp: (CmpOperator, bool),
context: String,
custom_message: Option<String>,
eval_context: &mut dyn EvalContext<'value, 'loc>,
) -> Result<EvaluationResult> {
let lhs = eval_context.query(lhs_query)?;
let results = cmp.compare(&lhs, rhs)?;
match results {
operators::EvalResult::Skip => Ok(EvaluationResult::EmptyQueryResult(Status::SKIP)),
operators::EvalResult::Result(results) => {
let mut statues: Vec<(QueryResult, Status)> = Vec::with_capacity(lhs.len());
for each in results {
match each {
operators::ValueEvalResult::LhsUnresolved(ur) => {
eval_context.start_record(&context)?;
eval_context.end_record(
&context,
RecordType::ClauseValueCheck(ClauseCheck::Comparison(
ComparisonClauseCheck {
status: Status::FAIL,
message: None,
custom_message: custom_message.clone(),
comparison: cmp,
from: QueryResult::UnResolved(ur.clone()),
to: None,
},
)),
)?;
statues.push((QueryResult::UnResolved(ur), Status::FAIL));
}
operators::ValueEvalResult::ComparisonResult(
operators::ComparisonResult::RhsUnresolved(urhs, lhs),
) => {
eval_context.start_record(&context)?;
eval_context.end_record(
&context,
RecordType::ClauseValueCheck(ClauseCheck::Comparison(
ComparisonClauseCheck {
status: Status::FAIL,
message: None,
custom_message: custom_message.clone(),
comparison: cmp,
from: QueryResult::Resolved(Rc::clone(&lhs)),
to: Some(QueryResult::UnResolved(urhs)),
},
)),
)?;
statues.push((QueryResult::Resolved(Rc::clone(&lhs)), Status::FAIL));
}
operators::ValueEvalResult::ComparisonResult(
operators::ComparisonResult::NotComparable(nc),
) => {
eval_context.start_record(&context)?;
eval_context.end_record(
&context,
RecordType::ClauseValueCheck(ClauseCheck::Comparison(
ComparisonClauseCheck {
status: Status::FAIL,
message: Some(nc.reason),
custom_message: custom_message.clone(),
comparison: cmp,
from: QueryResult::Resolved(Rc::clone(&nc.pair.lhs)),
to: Some(QueryResult::Resolved(nc.pair.rhs)),
},
)),
)?;
statues.push((QueryResult::Resolved(nc.pair.lhs), Status::FAIL));
}
operators::ValueEvalResult::ComparisonResult(
operators::ComparisonResult::Success(cmp),
) => match cmp {
operators::Compare::ListIn(lin) => {
eval_context.start_record(&context)?;
eval_context.end_record(
&context,
RecordType::ClauseValueCheck(ClauseCheck::Success),
)?;
statues.push((QueryResult::Resolved(lin.lhs), Status::PASS));
}
operators::Compare::QueryIn(qin) => {
for each in qin.lhs {
eval_context.start_record(&context)?;
eval_context.end_record(
&context,
RecordType::ClauseValueCheck(ClauseCheck::Success),
)?;
statues.push((QueryResult::Resolved(each), Status::PASS));
}
}
operators::Compare::Value(pair) => {
eval_context.start_record(&context)?;
eval_context.end_record(
&context,
RecordType::ClauseValueCheck(ClauseCheck::Success),
)?;
statues.push((QueryResult::Resolved(pair.lhs), Status::PASS));
}
operators::Compare::ValueIn(val) => {
eval_context.start_record(&context)?;
eval_context.end_record(
&context,
RecordType::ClauseValueCheck(ClauseCheck::Success),
)?;
statues.push((QueryResult::Resolved(val.lhs), Status::PASS));
}
},
operators::ValueEvalResult::ComparisonResult(
operators::ComparisonResult::Fail(cmpr),
) => match cmpr {
operators::Compare::Value(pair) => {
eval_context.start_record(&context)?;
eval_context.end_record(
&context,
RecordType::ClauseValueCheck(ClauseCheck::Comparison(
ComparisonClauseCheck {
status: Status::FAIL,
message: None,
custom_message: custom_message.clone(),
comparison: cmp,
from: QueryResult::Resolved(Rc::clone(&pair.lhs)),
to: Some(QueryResult::Resolved(pair.rhs)),
},
)),
)?;
statues
.push((QueryResult::Resolved(Rc::clone(&pair.lhs)), Status::FAIL));
}
operators::Compare::ValueIn(pair) => {
eval_context.start_record(&context)?;
eval_context.end_record(
&context,
RecordType::ClauseValueCheck(ClauseCheck::InComparison(
InComparisonCheck {
status: Status::FAIL,
message: None,
custom_message: custom_message.clone(),
comparison: cmp,
from: QueryResult::Resolved(Rc::clone(&pair.lhs)),
to: vec![QueryResult::Resolved(pair.rhs)],
},
)),
)?;
statues
.push((QueryResult::Resolved(Rc::clone(&pair.lhs)), Status::FAIL));
}
operators::Compare::ListIn(lin) => {
eval_context.start_record(&context)?;
eval_context.end_record(
&context,
RecordType::ClauseValueCheck(ClauseCheck::InComparison(
InComparisonCheck {
status: Status::FAIL,
message: None,
custom_message: custom_message.clone(),
comparison: cmp,
from: QueryResult::Resolved(Rc::clone(&lin.lhs)),
to: vec![QueryResult::Resolved(lin.rhs)],
},
)),
)?;
statues
.push((QueryResult::Resolved(Rc::clone(&lin.lhs)), Status::FAIL));
}
operators::Compare::QueryIn(qin) => {
let rhs = qin
.rhs
.iter()
.cloned()
.map(QueryResult::Resolved)
.collect::<Vec<_>>();
for lhs in qin.diff {
eval_context.start_record(&context)?;
eval_context.end_record(
&context,
RecordType::ClauseValueCheck(ClauseCheck::InComparison(
InComparisonCheck {
status: Status::FAIL,
message: None,
custom_message: custom_message.clone(),
comparison: cmp,
from: QueryResult::Resolved(Rc::clone(&lhs)),
to: rhs.clone(),
},
)),
)?;
statues
.push((QueryResult::Resolved(Rc::clone(&lhs)), Status::FAIL));
}
}
},
}
}
Ok(EvaluationResult::QueryValueResult(statues))
}
}
}
pub(super) fn real_binary_operation<'value, 'loc: 'value>(
lhs: &[QueryResult],
rhs: &[QueryResult],
cmp: (CmpOperator, bool),
context: String,
custom_message: Option<String>,
eval_context: &mut dyn EvalContext<'value, 'loc>,
) -> Result<EvaluationResult> {
let mut statues: Vec<(QueryResult, Status)> = Vec::with_capacity(lhs.len());
let cmp = if cmp.0 == CmpOperator::Eq && rhs.len() > 1 {
(CmpOperator::In, cmp.1)
} else {
cmp
};
for each in lhs.iter() {
match each {
QueryResult::UnResolved(_ur) => {
eval_context.start_record(&context)?;
eval_context.end_record(
&context,
RecordType::ClauseValueCheck(ClauseCheck::Comparison(ComparisonClauseCheck {
status: Status::FAIL,
message: None,
custom_message: custom_message.clone(),
comparison: cmp,
from: each.clone(),
to: None,
})),
)?;
statues.push((each.clone(), Status::FAIL));
}
QueryResult::Literal(l) | QueryResult::Resolved(l) => {
let r = match cmp {
(CmpOperator::Eq, is_not) => each_lhs_compare(
not_compare(crate::rules::path_value::compare_eq, is_not),
Rc::clone(l),
rhs,
)?,
(CmpOperator::Ge, is_not) => each_lhs_compare(
not_compare(crate::rules::path_value::compare_ge, is_not),
Rc::clone(l),
rhs,
)?,
(CmpOperator::Gt, is_not) => each_lhs_compare(
not_compare(crate::rules::path_value::compare_gt, is_not),
Rc::clone(l),
rhs,
)?,
(CmpOperator::Lt, is_not) => each_lhs_compare(
not_compare(crate::rules::path_value::compare_lt, is_not),
Rc::clone(l),
rhs,
)?,
(CmpOperator::Le, is_not) => each_lhs_compare(
not_compare(crate::rules::path_value::compare_le, is_not),
Rc::clone(l),
rhs,
)?,
(CmpOperator::In, is_not) => {
each_lhs_compare(in_cmp(is_not), Rc::clone(l), rhs)?
}
_ => unreachable!(),
};
match cmp.0 {
CmpOperator::In => {
statues.extend(report_at_least_one(
r,
cmp,
context.clone(),
custom_message.clone(),
eval_context,
)?);
}
_ => {
let status = report_all_values(
r,
cmp,
context.clone(),
custom_message.clone(),
eval_context,
)?;
statues.extend(status);
}
}
}
};
}
Ok(EvaluationResult::QueryValueResult(statues))
}
#[allow(clippy::never_loop)]
pub(in crate::rules) fn eval_guard_access_clause<'value, 'loc: 'value>(
gac: &'value GuardAccessClause<'loc>,
resolver: &mut dyn EvalContext<'value, 'loc>,
) -> Result<Status> {
let all = gac.access_clause.query.match_all;
let blk_context = format!("GuardAccessClause#block{}", gac);
resolver.start_record(&blk_context)?;
let statues = if gac.access_clause.comparator.0.is_unary() {
unary_operation(
&gac.access_clause.query.query,
gac.access_clause.comparator,
gac.negation,
format!("{}", gac),
gac.access_clause.custom_message.clone(),
resolver,
)
} else {
let (rhs, _) = match &gac.access_clause.compare_with {
Some(val) => match val {
LetValue::Value(rhs_val) => {
(vec![QueryResult::Literal(Rc::new(rhs_val.clone()))], true)
}
LetValue::AccessClause(acc_querty) => match resolver.query(&acc_querty.query) {
Ok(result) => (result, false),
Err(e) => {
resolver.end_record(
&blk_context,
RecordType::GuardClauseBlockCheck(BlockCheck {
status: Status::FAIL,
at_least_one_matches: !all,
message: Some(format!("Error {e} when handling clause, bailing")),
}),
)?;
return Err(e);
}
},
LetValue::FunctionCall(FunctionExpr {
parameters, name, ..
}) => match resolve_function(name, parameters, resolver) {
Ok(result) => (result, false),
Err(e) => {
resolver.end_record(
&blk_context,
RecordType::GuardClauseBlockCheck(BlockCheck {
status: Status::FAIL,
at_least_one_matches: !all,
message: Some(format!("Error {e} when handling clause, bailing")),
}),
)?;
return Err(e);
}
},
},
None => {
resolver.end_record(
&blk_context,
RecordType::GuardClauseBlockCheck(BlockCheck {
status: Status::FAIL,
at_least_one_matches: !all,
message: Some(
"Error not RHS for binary clause when handling clause, bailing"
.to_string(),
),
}),
)?;
return Err(Error::NotComparable(format!(
"GuardAccessClause {}, did not have a RHS for compare operation",
blk_context
)));
}
};
binary_operation(
&gac.access_clause.query.query,
&rhs,
gac.access_clause.comparator,
format!("{}", gac),
gac.access_clause.custom_message.clone(),
resolver,
)
};
match statues {
Ok(statues) => match statues {
EvaluationResult::EmptyQueryResult(status) => {
resolver.end_record(
&blk_context,
RecordType::GuardClauseBlockCheck(BlockCheck {
status,
message: None,
at_least_one_matches: all,
}),
)?;
Ok(status)
}
EvaluationResult::QueryValueResult(result) => {
let outcome = loop {
let mut fails = 0;
let mut pass = 0;
for (_value, status) in result {
match status {
Status::PASS => {
pass += 1;
}
Status::FAIL => {
fails += 1;
}
Status::SKIP => unreachable!(),
}
}
if all {
if fails > 0 {
break Status::FAIL;
}
break Status::PASS;
} else {
if pass > 0 {
break Status::PASS;
}
break Status::FAIL;
}
};
resolver.end_record(
&blk_context,
RecordType::GuardClauseBlockCheck(BlockCheck {
message: None,
status: outcome,
at_least_one_matches: !all,
}),
)?;
Ok(outcome)
}
},
Err(e) => {
resolver.end_record(
&blk_context,
RecordType::GuardClauseBlockCheck(BlockCheck {
status: Status::FAIL,
at_least_one_matches: !all,
message: Some(format!("Error {} when handling clause, bailing", e)),
}),
)?;
Err(e)
}
}
}
pub(in crate::rules) fn eval_guard_named_clause<'value, 'loc: 'value>(
gnc: &'value GuardNamedRuleClause<'loc>,
resolver: &mut dyn EvalContext<'value, 'loc>,
) -> Result<Status> {
let context = format!("{}", gnc);
resolver.start_record(&context)?;
match resolver.rule_status(&gnc.dependent_rule) {
Ok(status) => {
let status = match status {
Status::PASS => {
if gnc.negation {
Status::FAIL
} else {
Status::PASS
}
}
_ => {
if gnc.negation {
Status::PASS
} else {
Status::FAIL
}
}
};
match status {
Status::PASS => {
resolver
.end_record(&context, RecordType::ClauseValueCheck(ClauseCheck::Success))?;
}
Status::FAIL => {
resolver.end_record(
&context,
RecordType::ClauseValueCheck(ClauseCheck::DependentRule(
MissingValueCheck {
rule: &gnc.dependent_rule,
status: Status::FAIL,
message: None,
custom_message: gnc.custom_message.clone(),
},
)),
)?;
}
_ => unreachable!(),
}
Ok(status)
}
Err(e) => {
resolver.end_record(
&context,
RecordType::ClauseValueCheck(ClauseCheck::DependentRule(MissingValueCheck {
rule: &gnc.dependent_rule,
status: Status::FAIL,
message: Some(format!("{} failed due to error {}", context, e)),
custom_message: gnc.custom_message.clone(),
})),
)?;
Err(e)
}
}
}
pub(in crate::rules) fn eval_general_block_clause<'value, 'loc: 'value, T, E>(
block: &'value Block<'loc, T>,
resolver: &mut dyn EvalContext<'value, 'loc>,
eval_fn: E,
) -> Result<Status>
where
E: Fn(&'value T, &mut dyn EvalContext<'value, 'loc>) -> Result<Status>,
{
let mut block_scope = block_scope(block, resolver.root(), resolver);
eval_conjunction_clauses(&block.conjunctions, &mut block_scope, eval_fn)
}
pub(in crate::rules) fn eval_guard_block_clause<'value, 'loc: 'value>(
block_clause: &'value BlockGuardClause<'loc>,
resolver: &mut dyn EvalContext<'value, 'loc>,
) -> Result<Status> {
let context = format!("BlockGuardClause#{}", block_clause.location);
let match_all = block_clause.query.match_all;
resolver.start_record(&context)?;
let block_values = match resolver.query(&block_clause.query.query) {
Ok(values) => values,
Err(e) => {
resolver.end_record(
&context,
RecordType::BlockGuardCheck(BlockCheck {
status: Status::FAIL,
at_least_one_matches: !match_all,
message: None,
}),
)?;
return Err(e);
}
};
if block_values.is_empty() {
let status = if block_clause.not_empty {
Status::FAIL
} else {
Status::SKIP
};
resolver.end_record(
&context,
RecordType::BlockGuardCheck(BlockCheck {
status,
at_least_one_matches: !match_all,
message: None,
}),
)?;
return Ok(status);
}
let mut fails = 0;
let mut passes = 0;
for each in block_values {
match each {
QueryResult::UnResolved(ur) => {
fails += 1;
let guard_cxt = format!("GuardBlockAccessClause#{}", block_clause.location);
resolver.start_record(&guard_cxt)?;
resolver.end_record(
&guard_cxt,
RecordType::ClauseValueCheck(ClauseCheck::MissingBlockValue(ValueCheck {
message: Some(format!(
"Query {} did not resolve to correct value, reason {}",
SliceDisplay(&block_clause.query.query),
ur.reason.as_ref().map_or("", |s| s)
)),
status: Status::FAIL,
custom_message: None,
from: QueryResult::UnResolved(ur),
})),
)?;
}
QueryResult::Literal(rv) | QueryResult::Resolved(rv) => {
let mut val_resolver = ValueScope {
root: rv,
parent: resolver,
};
match eval_general_block_clause(
&block_clause.block,
&mut val_resolver,
eval_guard_clause,
) {
Ok(status) => match status {
Status::PASS => {
passes += 1;
}
Status::FAIL => {
fails += 1;
}
Status::SKIP => {}
},
Err(e) => {
resolver.end_record(
&context,
RecordType::BlockGuardCheck(BlockCheck {
status: Status::FAIL,
at_least_one_matches: !match_all,
message: Some(format!(
"Error {} when handling block clause, bailing",
e
)),
}),
)?;
return Err(e);
}
}
}
}
}
let status = if match_all {
if fails > 0 {
Status::FAIL
} else if passes > 0 {
Status::PASS
} else {
Status::SKIP
}
} else if passes > 0 {
Status::PASS
} else if fails > 0 {
Status::FAIL
} else {
Status::SKIP
};
resolver.end_record(
&context,
RecordType::BlockGuardCheck(BlockCheck {
status,
at_least_one_matches: !match_all,
message: None,
}),
)?;
Ok(status)
}
fn eval_when_condition_block<'value, 'loc: 'value>(
context: String,
conditions: &'value WhenConditions<'loc>,
block: &'value Block<'loc, GuardClause<'loc>>,
resolver: &mut dyn EvalContext<'value, 'loc>,
) -> Result<Status> {
resolver.start_record(&context)?;
let when_context = format!("{}/When", context);
resolver.start_record(&when_context)?;
let block = match eval_conjunction_clauses(conditions, resolver, eval_when_clause) {
Ok(status) => {
if status != Status::PASS {
resolver.end_record(&when_context, RecordType::WhenCondition(status))?;
resolver.end_record(
&context,
RecordType::WhenCheck(BlockCheck {
status: Status::SKIP,
at_least_one_matches: false,
message: None,
}),
)?;
return Ok(Status::SKIP);
}
resolver.end_record(&when_context, RecordType::WhenCondition(Status::PASS))?;
block
}
Err(e) => {
resolver.end_record(&when_context, RecordType::WhenCondition(Status::FAIL))?;
resolver.end_record(
&context,
RecordType::WhenCheck(BlockCheck {
status: Status::FAIL,
message: Some(format!(
"Error {} during type condition evaluation, bailing",
e
)),
at_least_one_matches: false,
}),
)?;
return Err(e);
}
};
Ok(
match eval_general_block_clause(block, resolver, eval_guard_clause) {
Ok(status) => {
resolver.end_record(
&context,
RecordType::WhenCheck(BlockCheck {
status,
message: None,
at_least_one_matches: false,
}),
)?;
status
}
Err(e) => {
resolver.end_record(
&context,
RecordType::WhenCheck(BlockCheck {
status: Status::FAIL,
message: Some(format!(
"Error {} during type condition evaluation, bailing",
e
)),
at_least_one_matches: false,
}),
)?;
return Err(e);
}
},
)
}
struct ResolvedParameterContext<'eval, 'value, 'loc: 'value> {
call_rule: &'value ParameterizedNamedRuleClause<'loc>,
resolved_parameters: HashMap<&'value str, Vec<QueryResult>>,
parent: &'eval mut dyn EvalContext<'value, 'loc>,
}
impl<'eval, 'value, 'loc: 'value> EvalContext<'value, 'loc>
for ResolvedParameterContext<'eval, 'value, 'loc>
{
fn query(&mut self, query: &'value [QueryPart<'loc>]) -> Result<Vec<QueryResult>> {
self.parent.query(query)
}
fn find_parameterized_rule(
&mut self,
rule_name: &str,
) -> Result<&'value ParameterizedRule<'loc>> {
self.parent.find_parameterized_rule(rule_name)
}
fn root(&mut self) -> Rc<PathAwareValue> {
self.parent.root()
}
fn rule_status(&mut self, rule_name: &'value str) -> Result<Status> {
self.parent.rule_status(rule_name)
}
fn resolve_variable(&mut self, variable_name: &'value str) -> Result<Vec<QueryResult>> {
match self.resolved_parameters.get(variable_name) {
Some(res) => Ok(res.clone()),
None => self.parent.resolve_variable(variable_name),
}
}
fn add_variable_capture_key(
&mut self,
variable_name: &'value str,
key: Rc<PathAwareValue>,
) -> Result<()> {
self.parent.add_variable_capture_key(variable_name, key)
}
}
impl<'eval, 'value, 'loc: 'value> RecordTracer<'value>
for ResolvedParameterContext<'eval, 'value, 'loc>
{
fn start_record(&mut self, context: &str) -> Result<()> {
self.parent.start_record(context)
}
fn end_record(&mut self, context: &str, record: RecordType<'value>) -> Result<()> {
let record = match record {
RecordType::RuleCheck(ns) => {
if ns.name == self.call_rule.named_rule.dependent_rule {
RecordType::RuleCheck(NamedStatus {
name: ns.name,
status: ns.status,
message: self.call_rule.named_rule.custom_message.clone(),
})
} else {
RecordType::RuleCheck(ns)
}
}
rest => rest,
};
self.parent.end_record(context, record)
}
}
pub(in crate::rules) fn eval_parameterized_rule_call<'value, 'loc: 'value>(
call_rule: &'value ParameterizedNamedRuleClause<'loc>,
resolver: &mut dyn EvalContext<'value, 'loc>,
) -> Result<Status> {
let param_rule = resolver.find_parameterized_rule(&call_rule.named_rule.dependent_rule)?;
if param_rule.parameter_names.len() != call_rule.parameters.len() {
return Err(Error::IncompatibleError(format!(
"Arity mismatch for called parameter rule {}, expected {}, got {}",
call_rule.named_rule.dependent_rule,
param_rule.parameter_names.len(),
call_rule.parameters.len()
)));
}
let mut resolved_parameters = HashMap::with_capacity(call_rule.parameters.len());
for (idx, each) in call_rule.parameters.iter().enumerate() {
match each {
LetValue::Value(val) => {
resolved_parameters.insert(
(param_rule.parameter_names[idx]).as_str(),
vec![QueryResult::Resolved(Rc::new(val.clone()))],
);
}
LetValue::AccessClause(query) => {
resolved_parameters.insert(
(param_rule.parameter_names[idx]).as_str(),
resolver.query(&query.query)?,
);
}
LetValue::FunctionCall(FunctionExpr {
parameters, name, ..
}) => {
let result = resolve_function(name, parameters, resolver)?;
resolved_parameters.insert((param_rule.parameter_names[idx]).as_str(), result);
}
}
}
let mut eval = ResolvedParameterContext {
parent: resolver,
resolved_parameters,
call_rule,
};
eval_rule(¶m_rule.rule, &mut eval)
}
pub(in crate::rules) fn eval_guard_clause<'value, 'loc: 'value>(
gc: &'value GuardClause<'loc>,
resolver: &mut dyn EvalContext<'value, 'loc>,
) -> Result<Status> {
match gc {
GuardClause::Clause(gac) => eval_guard_access_clause(gac, resolver),
GuardClause::NamedRule(gnc) => eval_guard_named_clause(gnc, resolver),
GuardClause::BlockClause(bc) => eval_guard_block_clause(bc, resolver),
GuardClause::WhenBlock(conditions, block) => eval_when_condition_block(
"GuardConditionClause".to_string(),
conditions,
block,
resolver,
),
GuardClause::ParameterizedNamedRule(prc) => eval_parameterized_rule_call(prc, resolver),
}
}
pub(in crate::rules) fn eval_when_clause<'value, 'loc: 'value>(
when_clause: &'value WhenGuardClause<'loc>,
resolver: &mut dyn EvalContext<'value, 'loc>,
) -> Result<Status> {
match when_clause {
WhenGuardClause::Clause(gac) => eval_guard_access_clause(gac, resolver),
WhenGuardClause::NamedRule(gnr) => eval_guard_named_clause(gnr, resolver),
WhenGuardClause::ParameterizedNamedRule(prc) => eval_parameterized_rule_call(prc, resolver),
}
}
pub(in crate::rules) fn eval_type_block_clause<'value, 'loc: 'value>(
type_block: &'value TypeBlock<'loc>,
resolver: &mut dyn EvalContext<'value, 'loc>,
) -> Result<Status> {
let context = format!("TypeBlock#{}", type_block.type_name);
resolver.start_record(&context)?;
let block = if let Some(conditions) = &type_block.conditions {
let when_context = format!("TypeBlock#{}/When", type_block.type_name);
resolver.start_record(&when_context)?;
match eval_conjunction_clauses(conditions, resolver, eval_when_clause) {
Ok(status) => {
if status != Status::PASS {
resolver.end_record(&when_context, RecordType::TypeCondition(status))?;
resolver.end_record(
&context,
RecordType::TypeCheck(TypeBlockCheck {
type_name: &type_block.type_name,
block: BlockCheck {
status: Status::SKIP,
at_least_one_matches: false,
message: None,
},
}),
)?;
return Ok(Status::SKIP);
}
resolver.end_record(&when_context, RecordType::TypeCondition(Status::PASS))?;
&type_block.block
}
Err(e) => {
resolver.end_record(&when_context, RecordType::TypeCondition(Status::FAIL))?;
resolver.end_record(
&context,
RecordType::TypeCheck(TypeBlockCheck {
type_name: &type_block.type_name,
block: BlockCheck {
status: Status::FAIL,
message: Some(format!(
"Error {} during type condition evaluation, bailing",
e
)),
at_least_one_matches: false,
},
}),
)?;
return Err(e);
}
}
} else {
&type_block.block
};
let values = match resolver.query(&type_block.query) {
Ok(values) => values,
Err(e) => {
resolver.end_record(
&context,
RecordType::TypeCheck(TypeBlockCheck {
type_name: &type_block.type_name,
block: BlockCheck {
status: Status::FAIL,
at_least_one_matches: false,
message: None,
},
}),
)?;
return Err(e);
}
};
if values.is_empty() {
resolver.end_record(
&context,
RecordType::TypeCheck(TypeBlockCheck {
type_name: &type_block.type_name,
block: BlockCheck {
status: Status::SKIP,
at_least_one_matches: false,
message: None,
},
}),
)?;
return Ok(Status::SKIP);
}
let mut fails = 0;
let mut passes = 0;
for (idx, each) in values.iter().enumerate() {
match each {
QueryResult::Literal(rv) | QueryResult::Resolved(rv) => {
let block_context = format!("{}/{}", context, idx);
resolver.start_record(&block_context)?;
let mut val_resolver = ValueScope {
root: Rc::clone(rv),
parent: resolver,
};
match eval_general_block_clause(block, &mut val_resolver, eval_guard_clause) {
Ok(status) => {
match status {
Status::PASS => {
passes += 1;
}
Status::FAIL => {
fails += 1;
}
Status::SKIP => {}
}
resolver.end_record(&block_context, RecordType::TypeBlock(status))?;
}
Err(e) => {
resolver.end_record(&block_context, RecordType::TypeBlock(Status::FAIL))?;
resolver.end_record(
&context,
RecordType::TypeCheck(TypeBlockCheck {
type_name: &type_block.type_name,
block: BlockCheck {
status: Status::FAIL,
message: Some(format!(
"Error {} during type block evaluation, bailing",
e
)),
at_least_one_matches: false,
},
}),
)?;
return Err(e);
}
}
}
QueryResult::UnResolved(ur) => {
resolver.end_record(
&context,
RecordType::TypeCheck(TypeBlockCheck {
type_name: &type_block.type_name,
block: BlockCheck {
at_least_one_matches: false,
status: Status::FAIL,
message: ur.reason.clone(),
},
}),
)?;
return Err(Error::MissingValue(format!(
"Unable to resolve type block query: {}",
type_block.type_name,
)));
}
}
}
let status = if fails > 0 {
Status::FAIL
} else if passes > 0 {
Status::PASS
} else {
Status::SKIP
};
resolver.end_record(
&context,
RecordType::TypeCheck(TypeBlockCheck {
type_name: &type_block.type_name,
block: BlockCheck {
status,
message: None,
at_least_one_matches: false,
},
}),
)?;
Ok(status)
}
pub(in crate::rules) fn eval_rule_clause<'value, 'loc: 'value>(
rule_clause: &'value RuleClause<'loc>,
resolver: &mut dyn EvalContext<'value, 'loc>,
) -> Result<Status> {
match rule_clause {
RuleClause::Clause(gc) => eval_guard_clause(gc, resolver),
RuleClause::TypeBlock(tb) => eval_type_block_clause(tb, resolver),
RuleClause::WhenBlock(conditions, block) => {
eval_when_condition_block("RuleClause".to_string(), conditions, block, resolver)
}
}
}
pub(in crate::rules) fn eval_rule<'value, 'loc: 'value>(
rule: &'value Rule<'loc>,
resolver: &mut dyn EvalContext<'value, 'loc>,
) -> Result<Status> {
let context = rule.rule_name.to_string();
resolver.start_record(&context)?;
let block = if let Some(conditions) = &rule.conditions {
let when_context = format!("Rule#{}/When", context);
resolver.start_record(&when_context)?;
match eval_conjunction_clauses(conditions, resolver, eval_when_clause) {
Ok(status) => {
if status != Status::PASS {
resolver.end_record(&when_context, RecordType::RuleCondition(status))?;
resolver.end_record(
&context,
RecordType::RuleCheck(NamedStatus {
status: Status::SKIP,
name: &rule.rule_name,
..Default::default()
}),
)?;
return Ok(Status::SKIP);
}
resolver.end_record(&when_context, RecordType::RuleCondition(Status::PASS))?;
&rule.block
}
Err(e) => {
resolver.end_record(&when_context, RecordType::RuleCondition(Status::FAIL))?;
resolver.end_record(
&context,
RecordType::RuleCheck(NamedStatus {
status: Status::FAIL,
name: &rule.rule_name,
..Default::default()
}),
)?;
return Err(e);
}
}
} else {
&rule.block
};
match eval_general_block_clause(block, resolver, eval_rule_clause) {
Ok(status) => {
resolver.end_record(
&context,
RecordType::RuleCheck(NamedStatus {
status,
name: &rule.rule_name,
..Default::default()
}),
)?;
Ok(status)
}
Err(e) => {
resolver.end_record(
&context,
RecordType::RuleCheck(NamedStatus {
status: Status::FAIL,
name: &rule.rule_name,
..Default::default()
}),
)?;
Err(e)
}
}
}
impl<'loc> std::fmt::Display for RulesFile<'loc> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("File(rules={})", self.guard_rules.len()))?;
Ok(())
}
}
pub(crate) fn eval_rules_file<'value, 'loc: 'value>(
rule: &'value RulesFile<'loc>,
resolver: &mut dyn EvalContext<'value, 'loc>,
data_file_name: Option<&'value str>,
) -> Result<Status> {
let context = format!("{}", rule);
resolver.start_record(&context)?;
let mut fails = 0;
let mut passes = 0;
for each_rule in &rule.guard_rules {
match eval_rule(each_rule, resolver) {
Ok(status) => match status {
Status::PASS => {
passes += 1;
}
Status::FAIL => {
fails += 1;
}
Status::SKIP => {}
},
Err(e) => {
resolver.end_record(
&context,
RecordType::RuleCheck(NamedStatus {
status: Status::FAIL,
name: &each_rule.rule_name,
..Default::default()
}),
)?;
return Err(e);
}
}
}
let overall = if fails > 0 {
Status::FAIL
} else if passes > 0 {
Status::PASS
} else {
Status::SKIP
};
resolver.end_record(
&context,
RecordType::FileCheck(NamedStatus {
status: overall,
name: data_file_name.unwrap_or_default(),
..Default::default()
}),
)?;
Ok(overall)
}
#[allow(clippy::never_loop)]
pub(in crate::rules) fn eval_conjunction_clauses<'value, 'loc: 'value, T, E>(
conjunctions: &'value Conjunctions<T>,
resolver: &mut dyn EvalContext<'value, 'loc>,
eval_fn: E,
) -> Result<Status>
where
E: Fn(&'value T, &mut dyn EvalContext<'value, 'loc>) -> Result<Status>,
{
Ok(loop {
let mut num_passes = 0;
let mut num_fails = 0;
let context = format!("{}#disjunction", std::any::type_name::<T>());
'conjunction: for conjunction in conjunctions {
let mut num_of_disjunction_fails = 0;
let multiple_ors_present = conjunction.len() > 1;
if multiple_ors_present {
resolver.start_record(&context)?;
}
for disjunction in conjunction {
match eval_fn(disjunction, resolver) {
Ok(status) => match status {
Status::PASS => {
num_passes += 1;
if multiple_ors_present {
resolver.end_record(
&context,
RecordType::Disjunction(BlockCheck {
message: None,
at_least_one_matches: true,
status: Status::PASS,
}),
)?;
}
continue 'conjunction;
}
Status::SKIP => {}
Status::FAIL => {
num_of_disjunction_fails += 1;
}
},
Err(e) => {
if multiple_ors_present {
resolver.end_record(
&context,
RecordType::Disjunction(BlockCheck {
message: Some(format!(
"Disjunction failed due to error {}, bailing",
e
)),
status: Status::FAIL,
at_least_one_matches: true,
}),
)?;
}
return Err(e);
}
}
}
if num_of_disjunction_fails > 0 {
num_fails += 1;
}
if multiple_ors_present {
if num_of_disjunction_fails > 0 {
resolver.end_record(
&context,
RecordType::Disjunction(BlockCheck {
message: None,
status: Status::FAIL,
at_least_one_matches: true,
}),
)?;
} else {
resolver.end_record(
&context,
RecordType::Disjunction(BlockCheck {
message: None,
status: Status::SKIP,
at_least_one_matches: true,
}),
)?;
}
}
}
if num_fails > 0 {
break Status::FAIL;
}
if num_passes > 0 {
break Status::PASS;
}
break Status::SKIP;
})
}
#[cfg(test)]
#[path = "eval_tests.rs"]
mod eval_tests;