use crate::error::Error;
use crate::error::Result;
#[derive(Debug, Default)]
pub struct Diagnostics {
problems: Vec<Error>,
}
impl Diagnostics {
pub fn new() -> Self {
return Self { problems: Vec::new() };
}
pub fn push(&mut self, problem: Error) {
match problem {
Error::Validation { problems } => self.problems.extend(problems),
leaf => self.problems.push(leaf),
}
}
pub fn check(&mut self, outcome: Result<()>) {
if let Err(problem) = outcome {
self.push(problem);
}
}
pub fn is_empty(&self) -> bool {
return self.problems.is_empty();
}
pub fn len(&self) -> usize {
return self.problems.len();
}
pub fn into_result(mut self) -> Result<()> {
if self.problems.len() == 1 {
match self.problems.pop() {
Some(single) => return Err(single),
None => return Ok(()),
}
}
if self.problems.is_empty() {
return Ok(());
}
return Err(Error::Validation {
problems: self.problems,
});
}
}
#[cfg(test)]
mod tests {
use super::*;
fn problem(name: &str) -> Error {
return Error::TypeNameCollision {
name: name.to_owned(),
artifact: "response enum".to_owned(),
hint: "rename it".to_owned(),
};
}
#[test]
fn empty_collector_is_ok() {
let diagnostics = Diagnostics::new();
assert!(diagnostics.is_empty());
assert!(diagnostics.into_result().is_ok());
}
#[test]
fn single_problem_is_reported_as_itself() {
let mut diagnostics = Diagnostics::new();
diagnostics.push(problem("Widget"));
let err = diagnostics.into_result().expect_err("one problem must fail");
assert!(
matches!(&err, Error::TypeNameCollision { name, .. } if name == "Widget"),
"expected the original variant unwrapped, got: {err:?}",
);
}
#[test]
fn multiple_problems_aggregate_in_order() {
let mut diagnostics = Diagnostics::new();
diagnostics.push(problem("Widget"));
diagnostics.check(Err(problem("Gadget")));
diagnostics.check(Ok(()));
assert_eq!(diagnostics.len(), 2, "a passing check must not be recorded");
let err = diagnostics.into_result().expect_err("two problems must fail");
let Error::Validation { problems } = &err else {
panic!("expected Validation, got: {err:?}");
};
assert_eq!(problems.len(), 2);
let message = err.to_string();
let widget = message.find("Widget").expect("message should list the first problem");
let gadget = message.find("Gadget").expect("message should list the second problem");
assert!(
widget < gadget,
"problems should be listed in discovery order: {message}"
);
assert!(
message.contains('2'),
"message should say how many problems were found: {message}",
);
}
#[test]
fn a_report_pushed_into_a_report_does_not_nest() {
let mut inner = Diagnostics::new();
inner.push(problem("Widget"));
inner.push(problem("Gadget"));
let report = inner.into_result().expect_err("two problems must fail");
let mut outer = Diagnostics::new();
outer.push(report);
outer.push(problem("Doohickey"));
assert_eq!(outer.len(), 3, "the inner problems should be counted one by one");
let err = outer.into_result().expect_err("three problems must fail");
let Error::Validation { problems } = &err else {
panic!("expected Validation, got: {err:?}");
};
assert!(
problems
.iter()
.all(|entry| return !matches!(*entry, Error::Validation { .. })),
"a report must hold leaf problems only, got: {problems:?}",
);
}
}