use colored::*; use serde::Serialize; use crate::commands::tracker::StatusContext; use crate::rules::eval_context::{ BinaryCheck, BinaryComparison, ClauseReport, EventRecord, FileReport, GuardClauseReport, InComparison, UnaryCheck, UnaryComparison, ValueComparisons, ValueUnResolved, }; use crate::rules::values::CmpOperator; use crate::rules::{ ClauseCheck, EvaluationType, NamedStatus, QueryResult, RecordType, Status, UnResolved, }; use fancy_regex::Regex; use lazy_static::*; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::convert::TryInto; use std::fmt::Debug; use std::hash::{Hash, Hasher}; use std::io::Write; #[derive(Debug, PartialEq, Serialize)] pub(super) struct Comparison { pub(super) operator: CmpOperator, pub(super) not_operator_exists: bool, } impl From<(CmpOperator, bool)> for Comparison { fn from(input: (CmpOperator, bool)) -> Self { Comparison { operator: input.0, not_operator_exists: input.1, } } } #[derive(Debug, PartialEq, Serialize)] pub(super) struct NameInfo<'a> { pub(super) rule: &'a str, pub(super) path: String, pub(super) provided: Option, pub(super) expected: Option, pub(super) comparison: Option, pub(super) message: String, pub(super) error: Option, } impl<'a> Default for NameInfo<'a> { fn default() -> Self { NameInfo { rule: "", path: "".to_string(), provided: None, expected: None, comparison: None, message: "".to_string(), error: None, } } } pub(super) trait GenericReporter: Debug { #[allow(clippy::too_many_arguments)] fn report( &self, writer: &mut dyn Write, rules_file_name: &str, data_file_name: &str, failed: HashMap>>, passed: HashSet, skipped: HashSet, longest_rule_len: usize, ) -> crate::rules::Result<()>; } #[derive(Debug)] #[allow(clippy::upper_case_acronyms)] pub(super) enum StructureType { JSON, YAML, } #[derive(Debug)] pub(super) struct StructuredSummary { hierarchy_type: StructureType, } impl StructuredSummary { pub(super) fn new(hierarchy_type: StructureType) -> Self { StructuredSummary { hierarchy_type } } } #[derive(Debug, Serialize)] struct DataOutput<'a> { data_from: &'a str, rules_from: &'a str, not_compliant: HashMap>>, not_applicable: HashSet, compliant: HashSet, } impl GenericReporter for StructuredSummary { fn report( &self, writer: &mut dyn Write, rules_file_name: &str, data_file_name: &str, failed: HashMap>>, passed: HashSet, skipped: HashSet, _: usize, ) -> crate::rules::Result<()> { let value = DataOutput { rules_from: rules_file_name, data_from: data_file_name, not_compliant: failed, compliant: passed, not_applicable: skipped, }; match &self.hierarchy_type { StructureType::JSON => writeln!(writer, "{}", serde_json::to_string(&value)?), StructureType::YAML => writeln!(writer, "{}", serde_yaml::to_string(&value)?), }?; Ok(()) } } lazy_static! { static ref PATH_FROM_MSG: Regex = Regex::new(r"path\s+=\s+(?P[^ ]+)").ok().unwrap(); } pub(super) fn find_failing_clauses<'record, 'value>( current: &'record EventRecord<'value>, ) -> Vec<&'record EventRecord<'value>> { match ¤t.container { Some(RecordType::Filter(_)) | Some(RecordType::ClauseValueCheck(ClauseCheck::Success)) => { vec![] } Some(RecordType::ClauseValueCheck(_)) => vec![current], Some(RecordType::RuleCheck(NamedStatus { message: Some(_), status: Status::FAIL, .. })) => vec![current], _ => { let mut acc = Vec::new(); for child in ¤t.children { acc.extend(find_failing_clauses(child)); } acc } } } pub(super) fn extract_name_info_from_record<'record>( rule_name: &'record str, clause: &'record EventRecord<'_>, ) -> crate::rules::Result> { Ok(match &clause.container { Some(RecordType::RuleCheck(NamedStatus { message: Some(msg), name, .. })) => NameInfo { message: msg.clone(), rule: name, ..Default::default() }, Some(RecordType::ClauseValueCheck(ClauseCheck::DependentRule(missing))) => NameInfo { error: missing.message.clone(), message: missing .custom_message .as_ref() .map_or("".to_string(), |m| m.clone()), rule: rule_name, ..Default::default() }, Some(RecordType::ClauseValueCheck(ClauseCheck::MissingBlockValue(missing))) => NameInfo { rule: rule_name, error: missing.message.clone(), message: missing .custom_message .as_ref() .map_or("".to_string(), |s| s.clone()), path: missing .from .unresolved_traversed_to() .map_or("".to_string(), |s| s.self_path().0.clone()), ..Default::default() }, Some(RecordType::ClauseValueCheck(ClauseCheck::Unary(check))) => match &check.value.from { QueryResult::Resolved(res) => { let (path, provided): (String, serde_json::Value) = (&**res).try_into()?; NameInfo { rule: rule_name, comparison: Some(check.comparison.into()), error: check.value.message.clone(), message: check .value .custom_message .as_ref() .map_or("".to_string(), |msg| msg.clone()), provided: Some(provided), path, ..Default::default() } } QueryResult::UnResolved(unres) => { let (path, provided): (String, serde_json::Value) = (&*unres.traversed_to).try_into()?; NameInfo { rule: rule_name, comparison: Some(check.comparison.into()), error: Some(check.value.message.as_ref().map_or( unres.reason.as_ref().map_or("".to_string(), |r| r.clone()), |msg| msg.clone(), )), message: check .value .custom_message .as_ref() .map_or("".to_string(), |msg| msg.clone()), provided: Some(provided), path, ..Default::default() } } QueryResult::Literal(_) => unreachable!(), }, Some(RecordType::ClauseValueCheck(ClauseCheck::Comparison(check))) => match &check.from { QueryResult::Literal(_) => unreachable!(), QueryResult::Resolved(res) => { let (path, provided): (String, serde_json::Value) = (&**res).try_into()?; let expected: Option<(String, serde_json::Value)> = match &check.to { Some(to) => match to { QueryResult::Literal(_) => unreachable!(), QueryResult::Resolved(v) => Some((&**v).try_into()?), QueryResult::UnResolved(ur) => Some((&*ur.traversed_to).try_into()?), }, None => None, }; let expected = expected.map(|(_, ex)| ex); NameInfo { rule: rule_name, comparison: Some(check.comparison.into()), error: check.message.clone(), message: check .custom_message .as_ref() .map_or("".to_string(), |msg| msg.clone()), provided: Some(provided), expected, path, } } QueryResult::UnResolved(unres) => { let (path, provided): (String, serde_json::Value) = (&*unres.traversed_to).try_into()?; NameInfo { rule: rule_name, comparison: Some(check.comparison.into()), error: Some(check.message.as_ref().map_or( unres.reason.as_ref().map_or("".to_string(), |r| r.clone()), |msg| msg.clone(), )), message: check .custom_message .as_ref() .map_or("".to_string(), |msg| msg.clone()), provided: Some(provided), path, ..Default::default() } } }, Some(RecordType::ClauseValueCheck(ClauseCheck::NoValueForEmptyCheck(msg))) => NameInfo { rule: rule_name, comparison: Some(Comparison { not_operator_exists: false, operator: CmpOperator::Empty, }), message: String::from(msg.as_ref().map_or("", |s| s.as_str())), ..Default::default() }, Some(RecordType::ClauseValueCheck(ClauseCheck::InComparison(incomp))) => { let provided = match incomp.from.resolved() { Some(val) => { let (_, value): (String, serde_json::Value) = (&*val).try_into()?; Some(value) } None => None, }; let mut to = Vec::new(); for each in &incomp.to { let (_, expected): (String, serde_json::Value) = match each { QueryResult::Literal(l) => (&**l).try_into()?, QueryResult::Resolved(v) => (&**v).try_into()?, QueryResult::UnResolved(ur) => (&*ur.traversed_to).try_into()?, }; to.push(expected); } NameInfo { rule: rule_name, comparison: Some(Comparison { not_operator_exists: incomp.comparison.1, operator: incomp.comparison.0, }), provided, expected: Some(serde_json::Value::Array(to)), message: String::from(incomp.message.as_ref().map_or("", |s| s.as_str())), ..Default::default() } } _ => unreachable!(), }) } pub(super) fn report_from_events( root_record: &EventRecord<'_>, writer: &mut dyn Write, data_file_name: &str, rules_file_name: &str, renderer: &dyn GenericReporter, ) -> crate::rules::Result<()> { let mut longest_rule_length = 0; let mut failed = HashMap::new(); let mut skipped = HashSet::new(); let mut success = HashSet::new(); for each_rule in &root_record.children { if let Some(RecordType::RuleCheck(NamedStatus { status, name, .. })) = &each_rule.container { if name.len() > longest_rule_length { longest_rule_length = name.len(); } match status { Status::FAIL => { let mut clauses = Vec::new(); for each_clause in find_failing_clauses(each_rule) { clauses.push(extract_name_info_from_record(name, each_clause)?); } failed.insert(name.to_string(), clauses); } Status::PASS => { success.insert(name.to_string()); } Status::SKIP => { skipped.insert(name.to_string()); } } } } renderer.report( writer, rules_file_name, data_file_name, failed, success, skipped, longest_rule_length, )?; Ok(()) } pub(super) fn extract_name_info<'a>( rule_name: &'a str, each_failing_clause: &StatusContext, ) -> crate::rules::Result> { if each_failing_clause.from.is_some() { let value = each_failing_clause.from.as_ref().unwrap(); let (path, from): (String, serde_json::Value) = value.try_into()?; Ok(NameInfo { rule: rule_name, path, provided: Some(from), expected: match &each_failing_clause.to { Some(to) => { let (_, val): (String, serde_json::Value) = to.try_into()?; Some(val) } None => None, }, comparison: each_failing_clause.comparator.map(|input| input.into()), message: each_failing_clause .msg .as_ref() .map_or("".to_string(), |e| { if !e.contains("DEFAULT") { e.clone() } else { "".to_string() } }), error: None, }) } else { // // This is crappy, but we are going to extract information from the retrieval error message // see path_value.rs for retrieval error messages. // TODO merge the query interface to retrieve partial results along with errored one ones and then // change this logic based on the reporting changes. Today we bail out for the first // retrieval error, fast fail semantics // // // No from is how we indicate retrieval errors. // let (path, error) = each_failing_clause .msg .as_ref() .map_or( ("".to_string(), "".to_string()), |msg| match PATH_FROM_MSG.captures(msg) { Ok(Some(cap)) => (cap["path"].to_string(), msg.clone()), Ok(None) => ("".to_string(), msg.clone()), Err(_) => panic!("Error while parsing retrieval errors"), }, ); Ok(NameInfo { rule: rule_name, path, error: Some(error), ..Default::default() }) } } pub(super) fn colored_string(status: Option) -> ColoredString { let status = match status { Some(s) => s, None => Status::SKIP, }; match status { Status::PASS => "PASS".green(), Status::FAIL => "FAIL".red().bold(), Status::SKIP => "SKIP".yellow().bold(), } } pub(super) fn find_all_failing_clauses(context: &StatusContext) -> Vec<&StatusContext> { let mut failed = Vec::with_capacity(context.children.len()); for each in &context.children { if each.status.map_or(false, |s| s == Status::FAIL) { match each.eval_type { EvaluationType::Clause | EvaluationType::BlockClause => { failed.push(each); if each.eval_type == EvaluationType::BlockClause { failed.extend(find_all_failing_clauses(each)); } } EvaluationType::Filter | EvaluationType::Condition => { continue; } _ => failed.extend(find_all_failing_clauses(each)), } } } failed } pub(super) fn print_compliant_skipped_info( writer: &mut dyn Write, passed: &HashSet, skipped: &HashSet, _: &str, data_file_name: &str, ) -> crate::rules::Result<()> { if !passed.is_empty() { writeln!(writer, "--")?; } for pass in passed { writeln!( writer, "Rule [{}] is compliant for template [{}]", pass, data_file_name )?; } if !skipped.is_empty() { writeln!(writer, "--")?; } for skip in skipped { writeln!( writer, "Rule [{}] is not applicable for template [{}]", skip, data_file_name )?; } Ok(()) } #[allow(clippy::too_many_arguments)] pub(super) fn print_name_info( writer: &mut dyn Write, info: &[NameInfo<'_>], _: usize, rules_file_name: &str, data_file_name: &str, retrieval_error: R, unary_message: U, binary_message: B, ) -> crate::rules::Result<()> where R: Fn(&str, &str, &NameInfo<'_>) -> crate::rules::Result, U: Fn(&str, &str, &str, &NameInfo<'_>) -> crate::rules::Result, B: Fn(&str, &str, &str, &NameInfo<'_>) -> crate::rules::Result, { for each in info { let _ = match &each.comparison { Some(cmp) => (Some(cmp.operator), cmp.not_operator_exists), None => (None, false), }; // CFN = Resource [] was not compliant with [] for property [] because provided value [] did not match expected value []. Error Message [] // General = Violation of [] for property [] because provided value [] did not match expected value []. Error Message [] // EQUALS failed at property path Properties.Encrypted because provided value [false] did not match with expected value [true]. match each.error { Some(_) => { // Block Clause retrieval error writeln!( writer, "{}", retrieval_error(rules_file_name, data_file_name, each)? )?; } None => { let (cmp, not) = match &each.comparison { Some(cmp) => (cmp.operator, cmp.not_operator_exists), None => { writeln!( writer, "Parameterized Rule {rule_name} failed for {data}. Reason {msg}", data = data_file_name, rule_name = each.rule, msg = each.message.replace('\n', "; ") )?; continue; } }; if cmp.is_unary() { use CmpOperator::*; writeln!( writer, "{}", unary_message( rules_file_name, data_file_name, match cmp { Exists => if !not { "did not exist" } else { "existed" }, Empty => if !not { "was not empty" } else { "was empty" }, IsList => if !not { "was not a list " } else { "was list" }, IsMap => if !not { "was not a struct" } else { "was struct" }, IsString => if !not { "was not a string " } else { "was string" }, IsBool => if !not { "was not a bool" } else { "was bool" }, IsInt => if !not { "was not an int" } else { "was int" }, // NOTE: This enum actually doesnt exist and this is why we are // seeing a warning for unreachable pattern underneath....Need to figure out what // happenned here... // IsFloat => // if !not { // "was not a float" // } else { // "was float" // }, Eq | In | Gt | Lt | Le | Ge => unreachable!(), }, each )?, )?; } else { // EQUALS failed at property path Properties.Encrypted because provided value [false] did not match with expected value [true]. writeln!( writer, "{}", binary_message( rules_file_name, data_file_name, if not { "did" } else { "did not" }, each )?, )?; } } } } Ok(()) } #[derive(Debug, Serialize)] struct DataOutputNewForm<'a, 'v> { data_from: &'a str, rules_from: &'a str, report: FileReport<'v>, } #[derive(Clone, Debug)] pub(super) struct LocalResourceAggr<'record, 'value: 'record> { pub(super) name: String, pub(super) resource_type: &'value str, pub(super) cdk_path: Option<&'value str>, pub(super) clauses: HashSet>>, pub(super) paths: BTreeSet, } #[derive(Clone, Debug)] pub(super) struct IdentityHash<'key, T> { pub(super) key: &'key T, } impl<'key, T> Hash for IdentityHash<'key, T> { fn hash(&self, state: &mut H) { std::ptr::hash(self.key, state) } } impl<'key, T> Eq for IdentityHash<'key, T> {} impl<'key, T> PartialEq for IdentityHash<'key, T> { fn eq(&self, other: &Self) -> bool { std::ptr::eq(self.key, other.key) } } #[derive(Clone, Debug)] #[allow(dead_code)] pub(super) struct Node<'report, 'value: 'report> { pub(super) parent: std::rc::Rc, pub(super) path: std::rc::Rc, pub(super) clause: &'report ClauseReport<'value>, } pub(super) type RuleHierarchy<'report, 'value> = BTreeMap, std::rc::Rc>>; pub(super) type PathTree<'report, 'value> = BTreeMap>>>; pub(super) fn insert_into_trees<'report, 'value: 'report>( clause: &'report ClauseReport<'value>, parent: std::rc::Rc, path_tree: &mut PathTree<'report, 'value>, hierarchy: &mut RuleHierarchy<'report, 'value>, ) { let path = std::rc::Rc::new(clause.key(&parent)); let node = std::rc::Rc::new(Node { parent, path: path.clone(), clause, }); hierarchy.insert(path, node.clone()); if let Some(from) = clause.value_from() { let path = from.self_path().0.to_string(); path_tree.entry(path).or_insert(vec![]).push(node.clone()); } if let Some(from) = clause.value_to() { let path = from.self_path().0.to_string(); path_tree.entry(path).or_insert(vec![]).push(node); } } pub(super) fn insert_into_trees_from_parent<'report, 'value: 'report>( clause: &'report ClauseReport<'value>, children: &'report [ClauseReport<'value>], parent: std::rc::Rc, path_tree: &mut PathTree<'report, 'value>, hierarchy: &mut RuleHierarchy<'report, 'value>, ) { let path = std::rc::Rc::new(clause.key(&parent)); let node = std::rc::Rc::new(Node { parent, path: path.clone(), clause, }); hierarchy.insert(path.clone(), node); for each in children { populate_hierarchy_path_trees(each, path.clone(), path_tree, hierarchy); } } pub(super) fn populate_hierarchy_path_trees<'report, 'value: 'report>( clause: &'report ClauseReport<'value>, parent: std::rc::Rc, path_tree: &mut PathTree<'report, 'value>, hierarchy: &mut RuleHierarchy<'report, 'value>, ) { match clause { ClauseReport::Clause(_) | ClauseReport::Block(_) => { insert_into_trees(clause, parent, path_tree, hierarchy) } ClauseReport::Disjunctions(ors) => { insert_into_trees_from_parent(clause, &ors.checks, parent, path_tree, hierarchy) } ClauseReport::Rule(rr) => { insert_into_trees_from_parent(clause, &rr.checks, parent, path_tree, hierarchy) } } } fn emit_messages( writer: &mut dyn Write, prefix: &str, message: &str, error: &str, width: usize, ) -> crate::rules::Result<()> { if !message.is_empty() { let message: Vec<&str> = if message.contains(';') { message.split(';').collect() } else if message.contains('\n') { message.split('\n').collect() } else { vec![message] }; let message: Vec<&str> = message .iter() .map(|s| s.trim_start().trim_end()) .filter(|s| !s.is_empty()) .collect(); if message.len() > 1 { writeln!( writer, "{prefix}{mh:, context: &str, message: &str, err_emitter: &mut dyn ComparisonErrorWriter, ) -> crate::rules::Result<()> { writeln!( writer, "{prefix}Check = {cxt} {{", prefix = prefix, cxt = context )?; let check_end = format!("{}}}", prefix); let prefix = format!("{} ", prefix); emit_messages(writer, &prefix, message, "", 0)?; writeln!(writer, "{prefix}RequiredPropertyError {{", prefix = prefix)?; let rpe_end = format!("{}}}", prefix); let prefix = format!("{} ", prefix); writeln!( writer, "{prefix}PropertyPath = {path}", prefix = prefix, path = vur.value.traversed_to.self_path() )?; writeln!( writer, "{prefix}MissingProperty = {prop}", prefix = prefix, prop = vur.value.remaining_query )?; let reason = vur.value.reason.as_ref().map_or("", String::as_str); if !reason.is_empty() { writeln!( writer, "{prefix}Reason = {reason}", prefix = prefix, reason = reason )?; } err_emitter.missing_property_msg(writer, clause, Some(&vur.value), &prefix)?; writeln!(writer, "{}", rpe_end)?; writeln!(writer, "{}", check_end)?; Ok(()) } pub(super) trait ComparisonErrorWriter { fn missing_property_msg( &mut self, _writer: &mut dyn Write, _cr: &ClauseReport<'_>, _bc: Option<&UnResolved>, _prefix: &str, ) -> crate::rules::Result { Ok(0) } fn binary_error_msg( &mut self, _writer: &mut dyn Write, _cr: &ClauseReport<'_>, _bc: &BinaryComparison, _prefix: &str, ) -> crate::rules::Result { Ok(0) } fn binary_error_in_msg( &mut self, _writer: &mut dyn Write, _cr: &ClauseReport<'_>, _bc: &InComparison, _prefix: &str, ) -> crate::rules::Result { Ok(0) } fn unary_error_msg( &mut self, _writer: &mut dyn Write, _cr: &ClauseReport<'_>, _bc: &UnaryComparison, _prefix: &str, ) -> crate::rules::Result { Ok(0) } } pub(super) fn pprint_clauses<'report, 'value: 'report>( writer: &mut dyn Write, clause: &'report ClauseReport<'value>, resource: &LocalResourceAggr<'report, 'value>, prefix: String, err_writer: &mut dyn ComparisonErrorWriter, ) -> crate::rules::Result<()> { match clause { ClauseReport::Rule(rr) => { writeln!( writer, "{prefix}Rule = {rule} {{", prefix = prefix, rule = rr.name.bright_magenta() )?; let rule_end = format!("{}}}", prefix); let prefix = format!("{} ", prefix); let message = rr .messages .custom_message .as_ref() .map_or("", String::as_str); let error = rr .messages .error_message .as_ref() .map_or("", String::as_str); emit_messages(writer, &prefix, message, error, 0)?; writeln!(writer, "{prefix}ALL {{", prefix = prefix)?; let all_end = format!("{}}}", prefix); let prefix = format!("{} ", prefix); for child in &rr.checks { pprint_clauses(writer, child, resource, prefix.clone(), err_writer)?; } writeln!(writer, "{}", all_end)?; writeln!(writer, "{}", rule_end)?; } ClauseReport::Disjunctions(ors) => { writeln!(writer, "{prefix}ANY {{", prefix = prefix)?; let end = format!("{}}}", prefix); let prefix = format!("{} ", prefix); for child in &ors.checks { pprint_clauses(writer, child, resource, prefix.clone(), err_writer)?; } writeln!(writer, "{}", end)?; } ClauseReport::Block(blk) => { if !resource.clauses.contains(&IdentityHash { key: clause }) { return Ok(()); } writeln!( writer, "{prefix}Check = {cxt} {{", prefix = prefix, cxt = blk.context )?; let check_end = format!("{}}}", prefix); let prefix = format!("{} ", prefix); writeln!(writer, "{prefix}RequiredPropertyError {{", prefix = prefix)?; let mpv_end = format!("{}}}", prefix); let prefix = format!("{} ", prefix); let (traversed_to, query) = blk.unresolved.as_ref().map_or(("", ""), |val| { (&val.traversed_to.self_path().0, &val.remaining_query) }); let width = if !traversed_to.is_empty() { let width = "MissingProperty".len() + 4; writeln!( writer, "{prefix}{pp: = Vec::new(); let width = std::cmp::max( width, err_writer.missing_property_msg( &mut post_message, clause, blk.unresolved.as_ref(), &prefix, )?, ); let message = blk .messages .custom_message .as_ref() .map_or("", String::as_str); let error = blk .messages .error_message .as_ref() .map_or("", String::as_str); emit_messages(writer, &prefix, message, error, width)?; writeln!( writer, "{}", match String::from_utf8(post_message) { Ok(msg) => msg, Err(_) => "".to_string(), } )?; writeln!(writer, "{}", mpv_end)?; writeln!(writer, "{}", check_end)?; } ClauseReport::Clause(gac) => { if !resource.clauses.contains(&IdentityHash { key: clause }) { return Ok(()); } match gac { GuardClauseReport::Unary(ur) => match &ur.check { UnaryCheck::UnResolved(vur) => { emit_retrieval_error( writer, &prefix, vur, clause, &ur.context, ur.messages .custom_message .as_ref() .map_or("", String::as_str), err_writer, )?; } UnaryCheck::Resolved(re) => { writeln!( writer, "{prefix}Check = {cxt} {{", prefix = prefix, cxt = ur.context )?; let check_end = format!("{}}}", prefix); let prefix = format!("{} ", prefix); writeln!(writer, "{prefix}ComparisonError {{", prefix = prefix)?; let ce_end = format!("{}}}", prefix); let prefix = format!("{} ", prefix); let mut post_message: Vec = Vec::new(); let width = err_writer.unary_error_msg(&mut post_message, clause, re, &prefix)?; let message = ur .messages .custom_message .as_ref() .map_or("", String::as_str); let error = ur .messages .error_message .as_ref() .map_or("", String::as_str); emit_messages(writer, &prefix, message, error, width)?; writeln!( writer, "{}", match String::from_utf8(post_message) { Ok(msg) => msg, Err(_) => "".to_string(), } )?; writeln!(writer, "{}", ce_end)?; writeln!(writer, "{}", check_end)?; } _ => {} }, GuardClauseReport::Binary(br) => match &br.check { BinaryCheck::UnResolved(vur) => { emit_retrieval_error( writer, &prefix, vur, clause, &br.context, br.messages .custom_message .as_ref() .map_or("", String::as_str), err_writer, )?; } BinaryCheck::Resolved(bc) => { writeln!( writer, "{prefix}Check = {cxt} {{", prefix = prefix, cxt = br.context )?; let check_end = format!("{}}}", prefix); let prefix = format!("{} ", prefix); writeln!(writer, "{prefix}ComparisonError {{", prefix = prefix)?; let ce_end = format!("{}}}", prefix); let prefix = format!("{} ", prefix); let mut post_message: Vec = Vec::new(); let width = err_writer.binary_error_msg(&mut post_message, clause, bc, &prefix)?; let message = br .messages .custom_message .as_ref() .map_or("", String::as_str); let error = br .messages .error_message .as_ref() .map_or("", String::as_str); emit_messages(writer, &prefix, message, error, width)?; writeln!( writer, "{}", match String::from_utf8(post_message) { Ok(msg) => msg, Err(_) => "".to_string(), } )?; writeln!(writer, "{}", ce_end)?; writeln!(writer, "{}", check_end)?; } BinaryCheck::InResolved(inr) => { writeln!( writer, "{prefix}Check = {cxt} {{", prefix = prefix, cxt = br.context )?; let check_end = format!("{}}}", prefix); let prefix = format!("{} ", prefix); writeln!(writer, "{prefix}ComparisonError {{", prefix = prefix)?; let ce_end = format!("{}}}", prefix); let prefix = format!("{} ", prefix); let mut post_message: Vec = Vec::new(); let width = err_writer.binary_error_in_msg( &mut post_message, clause, inr, &prefix, )?; let message = br .messages .custom_message .as_ref() .map_or("", String::as_str); let error = br .messages .error_message .as_ref() .map_or("", String::as_str); emit_messages(writer, &prefix, message, error, width)?; writeln!(writer, "{}", ce_end)?; writeln!( writer, "{}", match String::from_utf8(post_message) { Ok(msg) => msg, Err(_) => "".to_string(), } )?; writeln!(writer, "{}", check_end)?; } }, } } } Ok(()) }