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<serde_json::Value>,
pub(super) expected: Option<serde_json::Value>,
pub(super) comparison: Option<Comparison>,
pub(super) message: String,
pub(super) error: Option<String>,
}
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<String, Vec<NameInfo<'_>>>,
passed: HashSet<String>,
skipped: HashSet<String>,
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<String, Vec<NameInfo<'a>>>,
not_applicable: HashSet<String>,
compliant: HashSet<String>,
}
impl GenericReporter for StructuredSummary {
fn report(
&self,
writer: &mut dyn Write,
rules_file_name: &str,
data_file_name: &str,
failed: HashMap<String, Vec<NameInfo<'_>>>,
passed: HashSet<String>,
skipped: HashSet<String>,
_: 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<path>[^ ]+)").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<NameInfo<'record>> {
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<NameInfo<'a>> {
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 {
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<Status>) -> 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<String>,
skipped: &HashSet<String>,
_: &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<R, U, B>(
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<String>,
U: Fn(&str, &str, &str, &NameInfo<'_>) -> crate::rules::Result<String>,
B: Fn(&str, &str, &str, &NameInfo<'_>) -> crate::rules::Result<String>,
{
for each in info {
let _ = match &each.comparison {
Some(cmp) => (Some(cmp.operator), cmp.not_operator_exists),
None => (None, false),
};
match each.error {
Some(_) => {
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"
},
IsNull =>
if !not {
"was not null"
} else {
"was null"
},
IsFloat =>
if !not {
"was not a float"
} else {
"was float"
},
Eq | In | Gt | Lt | Le | Ge => unreachable!(),
},
each
)?,
)?;
} else {
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<IdentityHash<'record, ClauseReport<'value>>>,
pub(super) paths: BTreeSet<String>,
}
#[derive(Clone, Debug)]
pub(super) struct IdentityHash<'key, T> {
pub(super) key: &'key T,
}
impl<'key, T> Hash for IdentityHash<'key, T> {
fn hash<H: Hasher>(&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<String>,
pub(super) path: std::rc::Rc<String>,
pub(super) clause: &'report ClauseReport<'value>,
}
pub(super) type RuleHierarchy<'report, 'value> =
BTreeMap<std::rc::Rc<String>, std::rc::Rc<Node<'report, 'value>>>;
pub(super) type PathTree<'report, 'value> =
BTreeMap<String, Vec<std::rc::Rc<Node<'report, 'value>>>>;
pub(super) fn insert_into_trees<'report, 'value: 'report>(
clause: &'report ClauseReport<'value>,
parent: std::rc::Rc<String>,
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_default().push(node.clone());
}
if let Some(from) = clause.value_to() {
let path = from.self_path().0.to_string();
path_tree.entry(path).or_default().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<String>,
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<String>,
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:<width$} {{",
prefix = prefix,
mh = "Message",
width = width
)?;
for each in message {
writeln!(
writer,
"{prefix} {message}",
prefix = prefix,
message = each,
)?;
}
writeln!(writer, "{prefix}}}", prefix = prefix,)?;
} else {
writeln!(
writer,
"{prefix}{mh:<width$} = {message}",
prefix = prefix,
message = message[0],
mh = "Message",
width = width
)?;
}
}
if !error.is_empty() {
writeln!(
writer,
"{prefix}{eh:<width$} = {error}",
prefix = prefix,
error = error,
eh = "Error",
width = width
)?;
}
Ok(())
}
fn emit_retrieval_error(
writer: &mut dyn Write,
prefix: &str,
vur: &ValueUnResolved,
clause: &ClauseReport<'_>,
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<usize> {
Ok(0)
}
fn binary_error_msg(
&mut self,
_writer: &mut dyn Write,
_cr: &ClauseReport<'_>,
_bc: &BinaryComparison,
_prefix: &str,
) -> crate::rules::Result<usize> {
Ok(0)
}
fn binary_error_in_msg(
&mut self,
_writer: &mut dyn Write,
_cr: &ClauseReport<'_>,
_bc: &InComparison,
_prefix: &str,
) -> crate::rules::Result<usize> {
Ok(0)
}
fn unary_error_msg(
&mut self,
_writer: &mut dyn Write,
_cr: &ClauseReport<'_>,
_bc: &UnaryComparison,
_prefix: &str,
) -> crate::rules::Result<usize> {
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:<width$}= {path}\n{prefix}{mp:<width$}= {q}",
prefix = prefix,
pp = "PropertyPath",
width = width,
path = traversed_to,
mp = "MissingProperty",
q = query
)?;
width
} else {
"Message".len() + 4
};
let mut post_message: Vec<u8> = 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<u8> = 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<u8> = 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<u8> = 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(())
}