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
18pub(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 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 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 {
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 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 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 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 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 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 CmpOperator::In => {
599 let mut result = if clause.access_clause.comparator.1 {
600 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 }
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 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
1040fn 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;