cfn_guard/rules/
evaluate.rs

1use std::collections::HashMap;
2use std::convert::TryFrom;
3use std::fmt::Formatter;
4
5use crate::rules::errors::Error;
6use crate::rules::exprs::{
7    AccessQuery, Block, Conjunctions, GuardAccessClause, LetExpr, LetValue, Rule, RulesFile,
8    SliceDisplay,
9};
10use crate::rules::exprs::{
11    BlockGuardClause, GuardClause, GuardNamedRuleClause, QueryPart, RuleClause, TypeBlock,
12    WhenGuardClause,
13};
14use crate::rules::path_value::{PathAwareValue, QueryResolver};
15use crate::rules::values::*;
16use crate::rules::{Evaluate, EvaluationContext, EvaluationType, Result, Status};
17
18//////////////////////////////////////////////////////////////////////////////////////////////////
19//                                                                                              //
20//  Implementation for Guard Evaluations                                                        //
21//                                                                                              //
22//////////////////////////////////////////////////////////////////////////////////////////////////
23
24pub(super) fn resolve_variable_query<'s>(
25    all: bool,
26    variable: &str,
27    query: &[QueryPart<'_>],
28    var_resolver: &'s dyn EvaluationContext,
29) -> Result<Vec<&'s PathAwareValue>> {
30    let retrieved = var_resolver.resolve_variable(variable)?;
31    let index: usize = if query.len() > 1 {
32        match &query[1] {
33            QueryPart::AllIndices(_) => 2,
34            _ => 1,
35        }
36    } else {
37        1
38    };
39    let mut acc = Vec::with_capacity(retrieved.len());
40    for each in retrieved {
41        if query.len() > index {
42            acc.extend(each.select(all, &query[index..], var_resolver)?)
43        } else {
44            acc.push(each);
45        }
46    }
47    Ok(acc)
48}
49
50pub(super) fn resolve_query<'s>(
51    all: bool,
52    query: &[QueryPart<'_>],
53    context: &'s PathAwareValue,
54    var_resolver: &'s dyn EvaluationContext,
55) -> Result<Vec<&'s PathAwareValue>> {
56    match query[0].variable() {
57        Some(var) => resolve_variable_query(all, var, query, var_resolver),
58        None => context.select(all, query, var_resolver),
59    }
60}
61
62fn invert_status(status: Status, not: bool) -> Status {
63    if not {
64        return match status {
65            Status::FAIL => Status::PASS,
66            Status::PASS => Status::FAIL,
67            Status::SKIP => Status::SKIP,
68        };
69    }
70    status
71}
72
73fn negation_status(r: bool, clause_not: bool, not: bool) -> Status {
74    let status = if clause_not { !r } else { r };
75    let status = if not { !status } else { status };
76    if status {
77        Status::PASS
78    } else {
79        Status::FAIL
80    }
81}
82
83#[allow(clippy::type_complexity)]
84fn compare_loop_all<F>(
85    lhs: &Vec<&PathAwareValue>,
86    rhs: &Vec<&PathAwareValue>,
87    compare: F,
88    any_one_rhs: bool,
89) -> Result<(
90    bool,
91    Vec<(bool, Option<PathAwareValue>, Option<PathAwareValue>)>,
92)>
93where
94    F: Fn(&PathAwareValue, &PathAwareValue) -> Result<bool>,
95{
96    let mut lhs_cmp = true;
97    let mut results = Vec::with_capacity(lhs.len());
98    'lhs: for lhs_value in lhs {
99        let mut acc = Vec::with_capacity(lhs.len());
100        for rhs_value in rhs {
101            let check = compare(lhs_value, rhs_value)?;
102            if check {
103                if any_one_rhs {
104                    acc.clear();
105                    results.push((true, None, None));
106                    continue 'lhs;
107                }
108                acc.push((true, None, None));
109            } else {
110                acc.push((
111                    false,
112                    Some((*lhs_value).clone()),
113                    Some((*rhs_value).clone()),
114                ));
115                if !any_one_rhs {
116                    lhs_cmp = false;
117                }
118            }
119        }
120        if any_one_rhs {
121            lhs_cmp = false;
122        }
123        results.extend(acc)
124    }
125    Ok((lhs_cmp, results))
126}
127
128#[allow(clippy::never_loop, clippy::type_complexity)]
129fn compare_loop<F>(
130    lhs: &Vec<&PathAwareValue>,
131    rhs: &Vec<&PathAwareValue>,
132    compare: F,
133    any_one_rhs: bool,
134    atleast_one: bool,
135) -> Result<(
136    bool,
137    Vec<(bool, Option<PathAwareValue>, Option<PathAwareValue>)>,
138)>
139where
140    F: Fn(&PathAwareValue, &PathAwareValue) -> Result<bool>,
141{
142    let (overall, results) = compare_loop_all(lhs, rhs, compare, any_one_rhs)?;
143    let overall = 'outer: loop {
144        if !overall {
145            for (each, _, _) in results.iter() {
146                if atleast_one {
147                    if *each {
148                        break 'outer true;
149                    }
150                } else if !*each {
151                    break 'outer false;
152                }
153            }
154            if atleast_one {
155                break 'outer false;
156            } else {
157                break 'outer true;
158            }
159        } else {
160            break true;
161        }
162    };
163    Ok((overall, results))
164}
165
166fn elevate_inner<'a>(
167    list_of_list: &'a Vec<&PathAwareValue>,
168) -> Result<Vec<Vec<&'a PathAwareValue>>> {
169    let mut elevated = Vec::with_capacity(list_of_list.len());
170    for each_list_elem in list_of_list {
171        match *each_list_elem {
172            PathAwareValue::List((_path, list)) => {
173                let inner_lhs = list.iter().collect::<Vec<&PathAwareValue>>();
174                elevated.push(inner_lhs);
175            }
176
177            rest => elevated.push(vec![rest]),
178        }
179    }
180    Ok(elevated)
181}
182
183fn is_mixed_values_results(incoming: &[&PathAwareValue]) -> bool {
184    let mut non_list_elem = false;
185    let mut list_elem_present = false;
186    for each in incoming {
187        match each {
188            PathAwareValue::List((_, _)) => {
189                list_elem_present = true;
190                continue;
191            }
192
193            _ => {
194                non_list_elem = true;
195                continue;
196            }
197        }
198    }
199    non_list_elem && list_elem_present
200}
201
202fn merge_mixed_results<'a>(incoming: &'a [&PathAwareValue]) -> Vec<&'a PathAwareValue> {
203    let mut merged = Vec::with_capacity(incoming.len());
204    for each in incoming {
205        match each {
206            PathAwareValue::List((_, l)) => {
207                for inner in l {
208                    merged.push(inner);
209                }
210            }
211
212            rest => {
213                merged.push(rest);
214            }
215        }
216    }
217    merged
218}
219
220#[allow(clippy::type_complexity)]
221fn compare<F>(
222    lhs: &[&PathAwareValue],
223    _lhs_query: &[QueryPart<'_>],
224    rhs: &[&PathAwareValue],
225    _rhs_query: Option<&[QueryPart<'_>]>,
226    compare: F,
227    any: bool,
228    atleast_one: bool,
229) -> Result<(
230    Status,
231    Vec<(bool, Option<PathAwareValue>, Option<PathAwareValue>)>,
232)>
233where
234    F: Fn(&PathAwareValue, &PathAwareValue) -> Result<bool>,
235{
236    if lhs.is_empty() || rhs.is_empty() {
237        return Ok((Status::FAIL, vec![]));
238    }
239
240    let lhs = if is_mixed_values_results(lhs) {
241        merge_mixed_results(lhs)
242    } else {
243        lhs.to_vec()
244    };
245    let rhs = if is_mixed_values_results(rhs) {
246        merge_mixed_results(rhs)
247    } else {
248        rhs.to_vec()
249    };
250
251    let lhs_elem_has_list = lhs[0].is_list();
252    let rhs_elem_has_list = rhs[0].is_list();
253
254    //
255    // What are possible comparisons
256    //
257    if !lhs_elem_has_list && !rhs_elem_has_list {
258        match compare_loop(&lhs, &rhs, compare, any, atleast_one) {
259            Ok((true, outcomes)) => Ok((Status::PASS, outcomes)),
260            Ok((false, outcomes)) => Ok((Status::FAIL, outcomes)),
261            Err(e) => Err(e),
262        }
263    } else if lhs_elem_has_list && !rhs_elem_has_list {
264        for elevated in elevate_inner(&lhs)? {
265            if let Ok((cmp, outcomes)) =
266                compare_loop(&elevated, &rhs, |f, s| compare(f, s), any, atleast_one)
267            {
268                if !cmp {
269                    return Ok((Status::FAIL, outcomes));
270                }
271            }
272        }
273        Ok((Status::PASS, vec![]))
274    } else if (!lhs_elem_has_list || any) && rhs_elem_has_list {
275        for elevated in elevate_inner(&rhs)? {
276            if let Ok((cmp, outcomes)) =
277                compare_loop(&lhs, &elevated, |f, s| compare(f, s), any, atleast_one)
278            {
279                if !cmp {
280                    return Ok((Status::FAIL, outcomes));
281                }
282            }
283        }
284        Ok((Status::PASS, vec![]))
285    } else {
286        match compare_loop(&lhs, &rhs, compare, any, atleast_one)? {
287            (true, _) => Ok((Status::PASS, vec![])),
288            (false, outcomes) => Ok((Status::FAIL, outcomes)),
289        }
290    }
291}
292
293pub(super) fn invert_closure<F>(
294    f: F,
295    clause_not: bool,
296    not: bool,
297) -> impl Fn(&PathAwareValue, &PathAwareValue) -> Result<bool>
298where
299    F: Fn(&PathAwareValue, &PathAwareValue) -> Result<bool>,
300{
301    move |first, second| {
302        let r = f(first, second)?;
303        let r = if clause_not { !r } else { r };
304        let r = if not { !r } else { r };
305        Ok(r)
306    }
307}
308
309impl<'loc> Evaluate for GuardAccessClause<'loc> {
310    #[allow(clippy::never_loop)]
311    fn evaluate<'s>(
312        &self,
313        context: &'s PathAwareValue,
314        var_resolver: &'s dyn EvaluationContext,
315    ) -> Result<Status> {
316        //var_resolver.start_evaluation(EvaluationType::Clause, &guard_loc);
317        let clause = self;
318
319        let all = self.access_clause.query.match_all;
320
321        let (lhs, retrieve_error) = match resolve_query(
322            clause.access_clause.query.match_all,
323            &clause.access_clause.query.query,
324            context,
325            var_resolver,
326        ) {
327            Ok(v) => (Some(v), None),
328            Err(Error::RetrievalError(e)) | Err(Error::IncompatibleRetrievalError(e)) => {
329                (None, Some(e))
330            }
331            Err(e) => return Err(e),
332        };
333
334        let result = match clause.access_clause.comparator {
335            (CmpOperator::Empty, not) =>
336            //
337            // Retrieval Error is considered the same as an empty or !exists
338            // When using "SOME" keyword in the clause, then IncompatibleError is trapped to be none
339            // This is okay as long as the checks are for empty, exists
340            //
341            {
342                match &lhs {
343                    None => Some(negation_status(true, not, clause.negation)),
344                    Some(l) => Some(if !l.is_empty() {
345                        if l[0].is_list() || l[0].is_map() {
346                            'all_empty: loop {
347                                for element in l {
348                                    let status = match *element {
349                                        PathAwareValue::List((_, v)) => {
350                                            negation_status(v.is_empty(), not, clause.negation)
351                                        }
352                                        PathAwareValue::Map((_, m)) => {
353                                            negation_status(m.is_empty(), not, clause.negation)
354                                        }
355                                        _ => continue,
356                                    };
357
358                                    if status == Status::FAIL {
359                                        break 'all_empty Status::FAIL;
360                                    }
361                                }
362                                break Status::PASS;
363                            }
364                        } else {
365                            negation_status(false, not, clause.negation)
366                        }
367                    } else {
368                        negation_status(true, not, clause.negation)
369                    }),
370                }
371            }
372
373            (CmpOperator::Exists, not) => match &lhs {
374                None => Some(negation_status(false, not, clause.negation)),
375                Some(_) => Some(negation_status(true, not, clause.negation)),
376            },
377
378            (CmpOperator::Eq, not) => match &clause.access_clause.compare_with {
379                Some(LetValue::Value(PathAwareValue::Null(_))) => match &lhs {
380                    None => Some(negation_status(true, not, clause.negation)),
381                    Some(_) => Some(negation_status(false, not, clause.negation)),
382                },
383                _ => None,
384            },
385
386            (CmpOperator::IsString, not) => match &lhs {
387                None => Some(negation_status(false, not, clause.negation)),
388                Some(l) => Some(negation_status(
389                    l.iter()
390                        .find(|p| !matches!(**p, PathAwareValue::String(_)))
391                        .map_or(true, |_i| false),
392                    not,
393                    clause.negation,
394                )),
395            },
396
397            (CmpOperator::IsList, not) => match &lhs {
398                None => Some(negation_status(false, not, clause.negation)),
399                Some(l) => Some(negation_status(
400                    l.iter()
401                        .find(|p| !matches!(**p, PathAwareValue::List(_)))
402                        .map_or(true, |_i| false),
403                    not,
404                    clause.negation,
405                )),
406            },
407
408            (CmpOperator::IsMap, not) => match &lhs {
409                None => Some(negation_status(false, not, clause.negation)),
410                Some(l) => Some(negation_status(
411                    l.iter()
412                        .find(|p| !matches!(**p, PathAwareValue::Map(_)))
413                        .map_or(true, |_i| false),
414                    not,
415                    clause.negation,
416                )),
417            },
418
419            _ => None,
420        };
421
422        if let Some(r) = result {
423            let guard_loc = format!("{}", self);
424            let mut auto_reporter =
425                AutoReport::new(EvaluationType::Clause, var_resolver, &guard_loc);
426            let message = match &clause.access_clause.custom_message {
427                Some(msg) => msg,
428                None => "(DEFAULT: NO_MESSAGE)",
429            };
430            auto_reporter
431                .cmp(self.access_clause.comparator)
432                .status(r)
433                .from(match &lhs {
434                    None => Some(context.clone()),
435                    Some(l) => {
436                        if !l.is_empty() {
437                            Some(l[0].clone())
438                        } else {
439                            Some(context.clone())
440                        }
441                    }
442                });
443            if r == Status::FAIL {
444                auto_reporter.message(message.to_string());
445            }
446            return Ok(r);
447        }
448
449        let lhs = match lhs {
450            None => {
451                let guard_loc = format!("{}", self);
452                let mut auto_reporter =
453                    AutoReport::new(EvaluationType::Clause, var_resolver, &guard_loc);
454                if all {
455                    return Ok(auto_reporter
456                        .status(Status::FAIL)
457                        .message(retrieve_error.map_or("".to_string(), |e| e))
458                        .get_status());
459                } else {
460                    return Ok(auto_reporter
461                        .status(Status::FAIL)
462                        .message(retrieve_error.map_or("".to_string(), |e| e))
463                        .get_status());
464                }
465            }
466            Some(l) => l,
467        };
468
469        let rhs_local = match &clause.access_clause.compare_with {
470            None => {
471                return Err(Error::IncompatibleRetrievalError(format!(
472                    "Expecting a RHS for comparison and did not find one, clause@{}",
473                    clause.access_clause.location
474                )))
475            }
476
477            Some(expr) => match expr {
478                LetValue::Value(v) => Some(vec![v]),
479
480                _ => None,
481            },
482        };
483
484        let (rhs_resolved, rhs_query) = if let Some(expr) = &clause.access_clause.compare_with {
485            match expr {
486                LetValue::AccessClause(query) => (
487                    Some(resolve_query(
488                        query.match_all,
489                        &query.query,
490                        context,
491                        var_resolver,
492                    )?),
493                    Some(query.query.as_slice()),
494                ),
495                _ => (None, None),
496            }
497        } else {
498            (None, None)
499        };
500
501        let rhs = match rhs_local {
502            Some(local) => local,
503            None => match rhs_resolved {
504                Some(resolved) => resolved,
505                None => unreachable!(),
506            },
507        };
508
509        let (result, outcomes) = match &clause.access_clause.comparator.0 {
510            //
511            // ==, !=
512            //
513            CmpOperator::Eq => compare(
514                &lhs,
515                &clause.access_clause.query.query,
516                &rhs,
517                rhs_query,
518                invert_closure(
519                    super::path_value::compare_eq,
520                    clause.access_clause.comparator.1,
521                    clause.negation,
522                ),
523                false,
524                !all,
525            )?,
526
527            //
528            // >
529            //
530            CmpOperator::Gt => compare(
531                &lhs,
532                &clause.access_clause.query.query,
533                &rhs,
534                rhs_query,
535                invert_closure(
536                    super::path_value::compare_gt,
537                    clause.access_clause.comparator.1,
538                    clause.negation,
539                ),
540                false,
541                !all,
542            )?,
543
544            //
545            // >=
546            //
547            CmpOperator::Ge => compare(
548                &lhs,
549                &clause.access_clause.query.query,
550                &rhs,
551                rhs_query,
552                invert_closure(
553                    super::path_value::compare_ge,
554                    clause.access_clause.comparator.1,
555                    clause.negation,
556                ),
557                false,
558                !all,
559            )?,
560
561            //
562            // <
563            //
564            CmpOperator::Lt => compare(
565                &lhs,
566                &clause.access_clause.query.query,
567                &rhs,
568                rhs_query,
569                invert_closure(
570                    super::path_value::compare_lt,
571                    clause.access_clause.comparator.1,
572                    clause.negation,
573                ),
574                false,
575                !all,
576            )?,
577
578            //
579            // <=
580            //
581            CmpOperator::Le => compare(
582                &lhs,
583                &clause.access_clause.query.query,
584                &rhs,
585                rhs_query,
586                invert_closure(
587                    super::path_value::compare_le,
588                    clause.access_clause.comparator.1,
589                    clause.negation,
590                ),
591                false,
592                !all,
593            )?,
594
595            //
596            // IN, !IN
597            //
598            CmpOperator::In => {
599                let mut result = if clause.access_clause.comparator.1 {
600                    //
601                    // ! IN operator
602                    //
603                    compare(
604                        &lhs,
605                        &clause.access_clause.query.query,
606                        &rhs,
607                        rhs_query,
608                        |lhs, rhs| Ok(!super::path_value::compare_eq(lhs, rhs)?),
609                        false,
610                        !all,
611                    )?
612                } else {
613                    compare(
614                        &lhs,
615                        &clause.access_clause.query.query,
616                        &rhs,
617                        rhs_query,
618                        super::path_value::compare_eq,
619                        true,
620                        !all,
621                    )?
622                };
623                result.0 = invert_status(result.0, clause.negation);
624                result
625            }
626
627            _ => unreachable!(),
628        };
629
630        for (outcome, from, to) in outcomes {
631            let guard_loc = format!("{}", self);
632            let mut auto_reporter =
633                AutoReport::new(EvaluationType::Clause, var_resolver, &guard_loc);
634            auto_reporter.status(if outcome { Status::PASS } else { Status::FAIL });
635            auto_reporter.cmp(clause.access_clause.comparator);
636            if !outcome {
637                auto_reporter.from(from).to(to).message(
638                    match &clause.access_clause.custom_message {
639                        Some(msg) => msg.clone(),
640                        None => "DEFAULT MESSAGE(FAIL)".to_string(),
641                    },
642                );
643            }
644        }
645        Ok(result)
646    }
647}
648
649impl<'loc> std::fmt::Display for GuardNamedRuleClause<'loc> {
650    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
651        write!(f, "Rule({}@{})", self.dependent_rule, self.location)
652    }
653}
654
655impl<'loc> Evaluate for GuardNamedRuleClause<'loc> {
656    fn evaluate<'s>(
657        &self,
658        _context: &'s PathAwareValue,
659        var_resolver: &'s dyn EvaluationContext,
660    ) -> Result<Status> {
661        let guard_loc = format!("{}", self);
662        let mut auto_reporter = AutoReport::new(EvaluationType::Clause, var_resolver, &guard_loc);
663        let status = invert_status(
664            match var_resolver.rule_status(&self.dependent_rule)? {
665                Status::PASS => Status::PASS,
666                _ => Status::FAIL,
667            },
668            self.negation,
669        );
670
671        Ok(if status == Status::FAIL {
672            let msg = if let Some(msg) = &self.custom_message {
673                msg
674            } else {
675                "DEFAULT FAIL"
676            };
677            auto_reporter
678                .status(status)
679                .message(msg.to_string())
680                .get_status()
681        } else {
682            auto_reporter.status(status).get_status()
683        })
684    }
685}
686
687impl<'loc> Evaluate for GuardClause<'loc> {
688    #[allow(clippy::never_loop)]
689    fn evaluate<'s>(
690        &self,
691        context: &'s PathAwareValue,
692        var_resolver: &'s dyn EvaluationContext,
693    ) -> Result<Status> {
694        match self {
695            GuardClause::Clause(gac) => gac.evaluate(context, var_resolver),
696            GuardClause::NamedRule(nr) => nr.evaluate(context, var_resolver),
697            GuardClause::BlockClause(bc) => bc.evaluate(context, var_resolver),
698            GuardClause::WhenBlock(conditions, clauses) => {
699                let status = loop {
700                    let mut when_conditions =
701                        AutoReport::new(EvaluationType::Condition, var_resolver, "");
702                    break when_conditions
703                        .status(conditions.evaluate(context, var_resolver)?)
704                        .get_status();
705                };
706                match status {
707                    Status::PASS => {
708                        let mut auto_block =
709                            AutoReport::new(EvaluationType::ConditionBlock, var_resolver, "");
710                        Ok(auto_block
711                            .status(clauses.evaluate(context, var_resolver)?)
712                            .get_status())
713                    }
714                    _ => {
715                        let mut skip_block =
716                            AutoReport::new(EvaluationType::ConditionBlock, var_resolver, "");
717                        Ok(skip_block.status(Status::SKIP).get_status())
718                    }
719                }
720            }
721            GuardClause::ParameterizedNamedRule(_) => unimplemented!(),
722        }
723    }
724}
725
726impl<'loc, T: Evaluate + 'loc> Evaluate for Block<'loc, T> {
727    fn evaluate<'s>(
728        &self,
729        context: &'s PathAwareValue,
730        var_resolver: &'s dyn EvaluationContext,
731    ) -> Result<Status> {
732        let block = BlockScope::new(self, context, var_resolver)?;
733        self.conjunctions.evaluate(context, &block)
734    }
735}
736
737impl<'loc, T: Evaluate + 'loc> Evaluate for Conjunctions<T> {
738    #[allow(clippy::never_loop)]
739    fn evaluate<'s>(
740        &self,
741        context: &'s PathAwareValue,
742        var_resolver: &'s dyn EvaluationContext,
743    ) -> Result<Status> {
744        Ok(loop {
745            let mut num_passes = 0;
746            let mut num_fails = 0;
747            let item_name = std::any::type_name::<T>();
748            'conjunction: for conjunction in self {
749                let mut num_of_disjunction_fails = 0;
750                let mut report = if "cfn_guard::rules::exprs::GuardClause" == item_name {
751                    Some(AutoReport::new(
752                        EvaluationType::Conjunction,
753                        var_resolver,
754                        item_name,
755                    ))
756                } else {
757                    None
758                };
759                for disjunction in conjunction {
760                    match disjunction.evaluate(context, var_resolver)? {
761                        Status::PASS => {
762                            let _ = report
763                                .as_mut()
764                                .map(|r| Some(r.status(Status::PASS).get_status()));
765                            num_passes += 1;
766                            continue 'conjunction;
767                        }
768                        Status::SKIP => {}
769                        Status::FAIL => {
770                            num_of_disjunction_fails += 1;
771                        }
772                    }
773                }
774
775                if num_of_disjunction_fails > 0 {
776                    let _ = report
777                        .as_mut()
778                        .map(|r| Some(r.status(Status::FAIL).get_status()));
779                    num_fails += 1;
780                    continue;
781                    //break 'outer Status::FAIL
782                }
783            }
784            if num_fails > 0 {
785                break Status::FAIL;
786            }
787            if num_passes > 0 {
788                break Status::PASS;
789            }
790            break Status::SKIP;
791        })
792    }
793}
794
795impl<'loc> Evaluate for BlockGuardClause<'loc> {
796    #[allow(clippy::never_loop)]
797    fn evaluate<'s>(
798        &self,
799        context: &'s PathAwareValue,
800        var_resolver: &'s dyn EvaluationContext,
801    ) -> Result<Status> {
802        let blk_context = format!("Block[{}]", self.location);
803        let mut report = AutoReport::new(EvaluationType::BlockClause, var_resolver, &blk_context);
804        let all = self.query.match_all;
805        let block_values = match resolve_query(all, &self.query.query, context, var_resolver) {
806            Err(Error::RetrievalError(e)) | Err(Error::IncompatibleRetrievalError(e)) => {
807                return Ok(report.message(e).status(Status::FAIL).get_status())
808            }
809
810            Ok(v) => {
811                if v.is_empty() {
812                    // one or more
813                    return Ok(report
814                        .from(Some(context.clone()))
815                        .message(format!(
816                            "Query {} returned no results",
817                            SliceDisplay(&self.query.query)
818                        ))
819                        .status(Status::FAIL)
820                        .get_status());
821                } else {
822                    v
823                }
824            }
825
826            Err(e) => return Err(e),
827        };
828
829        Ok(report
830            .status(loop {
831                let mut num_fail = 0;
832                let mut num_pass = 0;
833                for each in block_values {
834                    match self.block.evaluate(each, var_resolver)? {
835                        Status::FAIL => {
836                            num_fail += 1;
837                        }
838                        Status::SKIP => {}
839                        Status::PASS => {
840                            num_pass += 1;
841                        }
842                    }
843                }
844
845                if all {
846                    if num_fail > 0 {
847                        break Status::FAIL;
848                    }
849                    if num_pass > 0 {
850                        break Status::PASS;
851                    }
852                    break Status::SKIP;
853                } else {
854                    if num_pass > 0 {
855                        break Status::PASS;
856                    }
857                    if num_fail > 0 {
858                        break Status::FAIL;
859                    }
860                    break Status::SKIP;
861                }
862            })
863            .get_status())
864    }
865}
866
867impl<'loc> Evaluate for WhenGuardClause<'loc> {
868    fn evaluate<'s>(
869        &self,
870        context: &'s PathAwareValue,
871        var_resolver: &'s dyn EvaluationContext,
872    ) -> Result<Status> {
873        match self {
874            WhenGuardClause::Clause(gac) => gac.evaluate(context, var_resolver),
875            WhenGuardClause::NamedRule(nr) => nr.evaluate(context, var_resolver),
876            WhenGuardClause::ParameterizedNamedRule(_) => todo!(),
877        }
878    }
879}
880
881impl<'loc> Evaluate for TypeBlock<'loc> {
882    #[allow(clippy::never_loop)]
883    fn evaluate<'s>(
884        &self,
885        context: &'s PathAwareValue,
886        var_resolver: &'s dyn EvaluationContext,
887    ) -> Result<Status> {
888        let mut type_report = AutoReport::new(EvaluationType::Type, var_resolver, &self.type_name);
889
890        if let Some(conditions) = &self.conditions {
891            let mut type_conds = AutoReport::new(EvaluationType::Condition, var_resolver, "");
892            match type_conds
893                .status(conditions.evaluate(context, var_resolver)?)
894                .get_status()
895            {
896                Status::PASS => {}
897                _ => return Ok(type_report.status(Status::SKIP).get_status()),
898            }
899        }
900
901        let query = format!("Resources.*[ Type == \"{}\" ]", self.type_name);
902        let cfn_query = AccessQuery::try_from(query.as_str())?;
903        let values = match context.select(cfn_query.match_all, &cfn_query.query, var_resolver) {
904            Ok(v) => {
905                if v.is_empty() {
906                    return Ok(type_report
907                        .message(format!(
908                            "There are no {} types present in context",
909                            self.type_name
910                        ))
911                        .status(Status::SKIP)
912                        .get_status());
913                } else {
914                    v
915                }
916            }
917            Err(_) => vec![context],
918        };
919
920        let overall = loop {
921            let mut num_fail = 0;
922            let mut num_pass = 0;
923            for (index, each) in values.iter().enumerate() {
924                let type_context = format!("{}#{}({})", self.type_name, index, (*each).self_path());
925                let mut each_type_report =
926                    AutoReport::new(EvaluationType::Type, var_resolver, &type_context);
927                match each_type_report
928                    .status(self.block.evaluate(each, var_resolver)?)
929                    .get_status()
930                {
931                    Status::PASS => {
932                        num_pass += 1;
933                    }
934                    Status::FAIL => {
935                        num_fail += 1;
936                    }
937                    Status::SKIP => {}
938                }
939            }
940            if num_fail > 0 {
941                break Status::FAIL;
942            }
943            if num_pass > 0 {
944                break Status::PASS;
945            }
946            break Status::SKIP;
947        };
948        Ok(match overall {
949            Status::SKIP => type_report
950                .status(Status::SKIP)
951                .message(format!(
952                    "ALL Clauses for all types {} was SKIPPED. This can be an error",
953                    self.type_name
954                ))
955                .get_status(),
956            rest => type_report.status(rest).get_status(),
957        })
958    }
959}
960
961impl<'loc> Evaluate for RuleClause<'loc> {
962    fn evaluate<'s>(
963        &self,
964        context: &'s PathAwareValue,
965        var_resolver: &'s dyn EvaluationContext,
966    ) -> Result<Status> {
967        Ok(match self {
968            RuleClause::Clause(gc) => gc.evaluate(context, var_resolver)?,
969            RuleClause::TypeBlock(tb) => tb.evaluate(context, var_resolver)?,
970            RuleClause::WhenBlock(conditions, block) => {
971                let status = {
972                    let mut auto_cond =
973                        AutoReport::new(EvaluationType::Condition, var_resolver, "");
974                    auto_cond
975                        .status(conditions.evaluate(context, var_resolver)?)
976                        .get_status()
977                };
978
979                match status {
980                    Status::PASS => {
981                        let mut auto_block =
982                            AutoReport::new(EvaluationType::ConditionBlock, var_resolver, "");
983                        auto_block
984                            .status(block.evaluate(context, var_resolver)?)
985                            .get_status()
986                    }
987                    _ => {
988                        let mut skip_block =
989                            AutoReport::new(EvaluationType::ConditionBlock, var_resolver, "");
990                        skip_block.status(Status::SKIP).get_status()
991                    }
992                }
993            }
994        })
995    }
996}
997
998impl<'loc> Evaluate for Rule<'loc> {
999    fn evaluate<'s>(
1000        &self,
1001        context: &'s PathAwareValue,
1002        var_resolver: &'s dyn EvaluationContext,
1003    ) -> Result<Status> {
1004        let mut auto = AutoReport::new(EvaluationType::Rule, var_resolver, &self.rule_name);
1005        if let Some(conds) = &self.conditions {
1006            let mut cond =
1007                AutoReport::new(EvaluationType::Condition, var_resolver, &self.rule_name);
1008            match cond
1009                .status(conds.evaluate(context, var_resolver)?)
1010                .get_status()
1011            {
1012                Status::PASS => {}
1013                _ => return Ok(auto.status(Status::SKIP).get_status()),
1014            }
1015        }
1016        Ok(auto
1017            .status(self.block.evaluate(context, var_resolver)?)
1018            .get_status())
1019    }
1020}
1021
1022impl<'loc> Evaluate for RulesFile<'loc> {
1023    fn evaluate<'s>(
1024        &self,
1025        context: &'s PathAwareValue,
1026        var_resolver: &'s dyn EvaluationContext,
1027    ) -> Result<Status> {
1028        let mut overall = Status::PASS;
1029        let mut auto_report = AutoReport::new(EvaluationType::File, var_resolver, "");
1030        for rule in &self.guard_rules {
1031            if Status::FAIL == rule.evaluate(context, var_resolver)? {
1032                overall = Status::FAIL
1033            }
1034        }
1035        auto_report.status(overall);
1036        Ok(overall)
1037    }
1038}
1039
1040//////////////////////////////////////////////////////////////////////////////////////////////////
1041//                                                                                              //
1042// Evaluation Context implementations for scoped variables                                      //
1043//                                                                                              //
1044//////////////////////////////////////////////////////////////////////////////////////////////////
1045
1046fn extract_variables<'s, 'loc>(
1047    expressions: &'s Vec<LetExpr<'loc>>,
1048    vars: &mut HashMap<&'s str, &'s PathAwareValue>,
1049    queries: &mut HashMap<&'s str, &'s AccessQuery<'loc>>,
1050) -> Result<()> {
1051    for each in expressions {
1052        match &each.value {
1053            LetValue::Value(v) => {
1054                vars.insert(&each.var, v);
1055            }
1056
1057            LetValue::AccessClause(query) => {
1058                queries.insert(&each.var, query);
1059            }
1060            LetValue::FunctionCall(_) => {}
1061        }
1062    }
1063    Ok(())
1064}
1065
1066#[derive(Debug)]
1067#[deprecated]
1068#[allow(dead_code)]
1069pub(crate) struct RootScope<'s, 'loc> {
1070    rules: &'s RulesFile<'loc>,
1071    input_context: &'s PathAwareValue,
1072    pending_queries: HashMap<&'s str, &'s AccessQuery<'loc>>,
1073    variables: std::cell::RefCell<HashMap<&'s str, Vec<&'s PathAwareValue>>>,
1074    literals: HashMap<&'s str, &'s PathAwareValue>,
1075    rule_by_name: HashMap<&'s str, &'s Rule<'loc>>,
1076    rule_statues: std::cell::RefCell<HashMap<&'s str, Status>>,
1077}
1078
1079#[cfg(test)]
1080impl<'s, 'loc> RootScope<'s, 'loc> {
1081    pub(crate) fn new(rules: &'s RulesFile<'loc>, value: &'s PathAwareValue) -> Result<Self> {
1082        let mut literals = HashMap::new();
1083        let mut pending = HashMap::new();
1084        extract_variables(&rules.assignments, &mut literals, &mut pending)?;
1085        let mut lookup_cache = HashMap::with_capacity(rules.guard_rules.len());
1086        for rule in &rules.guard_rules {
1087            lookup_cache.insert(rule.rule_name.as_str(), rule);
1088        }
1089
1090        Ok(RootScope {
1091            rules,
1092            input_context: value,
1093            pending_queries: pending,
1094            literals,
1095            variables: std::cell::RefCell::new(HashMap::new()),
1096            rule_by_name: lookup_cache,
1097            rule_statues: std::cell::RefCell::new(HashMap::with_capacity(rules.guard_rules.len())),
1098        })
1099    }
1100}
1101
1102impl<'s, 'loc> EvaluationContext for RootScope<'s, 'loc> {
1103    fn resolve_variable(&self, variable: &str) -> Result<Vec<&PathAwareValue>> {
1104        if let Some(literal) = self.literals.get(variable) {
1105            return Ok(vec![literal]);
1106        }
1107
1108        if let Some(value) = self.variables.borrow().get(variable) {
1109            return Ok(value.clone());
1110        }
1111        return if let Some((key, query)) = self.pending_queries.get_key_value(variable) {
1112            let all = query.match_all;
1113            let query = &query.query;
1114            let values = match query[0].variable() {
1115                Some(var) => resolve_variable_query(all, var, query, self)?,
1116                None => {
1117                    let values = self.input_context.select(all, query, self)?;
1118                    self.variables.borrow_mut().insert(*key, values.clone());
1119                    values
1120                }
1121            };
1122            Ok(values)
1123        } else {
1124            Err(Error::MissingVariable(format!(
1125                "Could not resolve variable {}",
1126                variable
1127            )))
1128        };
1129    }
1130
1131    fn rule_status(&self, rule_name: &str) -> Result<Status> {
1132        if let Some(status) = self.rule_statues.borrow().get(rule_name) {
1133            return Ok(*status);
1134        }
1135
1136        if let Some((name, rule)) = self.rule_by_name.get_key_value(rule_name) {
1137            let status = (*rule).evaluate(self.input_context, self)?;
1138            self.rule_statues.borrow_mut().insert(*name, status);
1139            return Ok(status);
1140        }
1141
1142        Err(Error::MissingValue(format!(
1143            "Attempting to resolve rule_status for rule = {}, rule not found",
1144            rule_name
1145        )))
1146    }
1147
1148    fn end_evaluation(
1149        &self,
1150        eval_type: EvaluationType,
1151        context: &str,
1152        _msg: String,
1153        _from: Option<PathAwareValue>,
1154        _to: Option<PathAwareValue>,
1155        status: Option<Status>,
1156        _cmp: Option<(CmpOperator, bool)>,
1157    ) {
1158        if EvaluationType::Rule == eval_type {
1159            let (name, _rule) = self.rule_by_name.get_key_value(context).unwrap();
1160            if let Some(status) = status {
1161                self.rule_statues.borrow_mut().insert(*name, status);
1162            }
1163        }
1164    }
1165
1166    fn start_evaluation(&self, _eval_type: EvaluationType, _context: &str) {}
1167}
1168
1169#[allow(dead_code)]
1170pub(crate) struct BlockScope<'s, T> {
1171    block_type: &'s Block<'s, T>,
1172    input_context: &'s PathAwareValue,
1173    pending_queries: HashMap<&'s str, &'s AccessQuery<'s>>,
1174    literals: HashMap<&'s str, &'s PathAwareValue>,
1175    variables: std::cell::RefCell<HashMap<&'s str, Vec<&'s PathAwareValue>>>,
1176    parent: &'s dyn EvaluationContext,
1177}
1178
1179impl<'s, T> BlockScope<'s, T> {
1180    pub(crate) fn new(
1181        block_type: &'s Block<'s, T>,
1182        context: &'s PathAwareValue,
1183        parent: &'s dyn EvaluationContext,
1184    ) -> Result<Self> {
1185        let mut literals = HashMap::new();
1186        let mut pending = HashMap::new();
1187        extract_variables(&block_type.assignments, &mut literals, &mut pending)?;
1188        Ok(BlockScope {
1189            block_type,
1190            input_context: context,
1191            literals,
1192            parent,
1193            variables: std::cell::RefCell::new(HashMap::new()),
1194            pending_queries: pending,
1195        })
1196    }
1197}
1198
1199impl<'s, T> EvaluationContext for BlockScope<'s, T> {
1200    fn resolve_variable(&self, variable: &str) -> Result<Vec<&PathAwareValue>> {
1201        if let Some(literal) = self.literals.get(variable) {
1202            return Ok(vec![literal]);
1203        }
1204
1205        if let Some(value) = self.variables.borrow().get(variable) {
1206            return Ok(value.clone());
1207        }
1208        return if let Some((key, query)) = self.pending_queries.get_key_value(variable) {
1209            let all = query.match_all;
1210            let query = &query.query;
1211            let values = match query[0].variable() {
1212                Some(var) => resolve_variable_query(all, var, query, self)?,
1213                None => {
1214                    let values = self.input_context.select(all, query, self)?;
1215                    self.variables.borrow_mut().insert(*key, values.clone());
1216                    values
1217                }
1218            };
1219            Ok(values)
1220        } else {
1221            self.parent.resolve_variable(variable)
1222        };
1223    }
1224
1225    fn rule_status(&self, rule_name: &str) -> Result<Status> {
1226        self.parent.rule_status(rule_name)
1227    }
1228
1229    fn end_evaluation(
1230        &self,
1231        eval_type: EvaluationType,
1232        context: &str,
1233        msg: String,
1234        from: Option<PathAwareValue>,
1235        to: Option<PathAwareValue>,
1236        status: Option<Status>,
1237        cmp: Option<(CmpOperator, bool)>,
1238    ) {
1239        self.parent
1240            .end_evaluation(eval_type, context, msg, from, to, status, cmp)
1241    }
1242
1243    fn start_evaluation(&self, eval_type: EvaluationType, context: &str) {
1244        self.parent.start_evaluation(eval_type, context);
1245    }
1246}
1247
1248#[derive(Clone)]
1249pub(super) struct AutoReport<'s> {
1250    context: &'s dyn EvaluationContext,
1251    type_context: &'s str,
1252    eval_type: EvaluationType,
1253    status: Option<Status>,
1254    from: Option<PathAwareValue>,
1255    to: Option<PathAwareValue>,
1256    cmp: Option<(CmpOperator, bool)>,
1257    message: Option<String>,
1258}
1259
1260impl<'s> std::fmt::Debug for AutoReport<'s> {
1261    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1262        f.write_fmt(format_args!(
1263            "Context = {}, Type = {}, Status = {:?}",
1264            self.type_context, self.eval_type, self.status
1265        ))?;
1266        Ok(())
1267    }
1268}
1269
1270impl<'s> AutoReport<'s> {
1271    pub(super) fn new(
1272        eval_type: EvaluationType,
1273        context: &'s dyn EvaluationContext,
1274        type_context: &'s str,
1275    ) -> Self {
1276        context.start_evaluation(eval_type, type_context);
1277        AutoReport {
1278            eval_type,
1279            type_context,
1280            context,
1281            status: None,
1282            from: None,
1283            to: None,
1284            cmp: None,
1285            message: None,
1286        }
1287    }
1288
1289    pub(super) fn status(&mut self, status: Status) -> &mut Self {
1290        self.status = Some(status);
1291        self
1292    }
1293
1294    pub(super) fn from(&mut self, from: Option<PathAwareValue>) -> &mut Self {
1295        self.from = from;
1296        self
1297    }
1298
1299    pub(super) fn to(&mut self, to: Option<PathAwareValue>) -> &mut Self {
1300        self.to = to;
1301        self
1302    }
1303
1304    pub(super) fn cmp(&mut self, cmp: (CmpOperator, bool)) -> &mut Self {
1305        self.cmp = Some(cmp);
1306        self
1307    }
1308
1309    pub(super) fn message(&mut self, msg: String) -> &mut Self {
1310        self.message = Some(msg);
1311        self
1312    }
1313
1314    pub(super) fn get_status(&self) -> Status {
1315        self.status.unwrap()
1316    }
1317}
1318
1319impl<'s> Drop for AutoReport<'s> {
1320    fn drop(&mut self) {
1321        let status = match self.status {
1322            Some(status) => status,
1323            None => Status::SKIP,
1324        };
1325        self.context.end_evaluation(
1326            self.eval_type,
1327            self.type_context,
1328            match &self.message {
1329                Some(message) => message.clone(),
1330                None => format!("DEFAULT MESSAGE({})", status),
1331            },
1332            self.from.clone(),
1333            self.to.clone(),
1334            Some(status),
1335            self.cmp,
1336        )
1337    }
1338}
1339
1340#[cfg(test)]
1341#[path = "evaluate_tests.rs"]
1342mod evaluate_tests;