#![deny(missing_docs)]
use std::{fmt::Debug, iter::Iterator};
#[derive(Debug, Default)]
pub struct StatusTracker {
error_behavior: ErrorBehavior,
logged_items: Vec<LogItem>,
ingredient_uris: Vec<String>,
current_uri: Vec<String>,
}
impl StatusTracker {
pub fn with_error_behavior(error_behavior: ErrorBehavior) -> Self {
Self {
error_behavior,
logged_items: vec![],
ingredient_uris: vec![],
current_uri: vec![],
}
}
pub fn logged_items(&self) -> &[LogItem] {
&self.logged_items
}
pub fn logged_items_mut(&mut self) -> &mut [LogItem] {
&mut self.logged_items
}
pub fn append(&mut self, other: &StatusTracker) {
for log_item in other.logged_items() {
self.add_non_error(log_item.clone());
}
}
pub fn add_non_error(&mut self, mut log_item: LogItem) {
if let Some(ingredient_uri) = self.ingredient_uris.last() {
log_item.ingredient_uri = Some(ingredient_uri.to_string().into());
}
if log_item.label.is_empty() {
if let Some(current_uri) = self.current_uri.last() {
log_item.label = std::borrow::Cow::Owned(current_uri.to_string());
}
}
log::debug!("Validation info: {log_item:#?}");
self.logged_items.push(log_item);
}
pub fn add_error<E>(&mut self, mut log_item: LogItem, err: E) -> Result<E, E> {
if let Some(ingredient_uri) = self.ingredient_uris.last() {
log_item.ingredient_uri = Some(ingredient_uri.to_string().into());
}
if log_item.label.is_empty() {
if let Some(current_uri) = self.current_uri.last() {
log_item.label = std::borrow::Cow::Owned(current_uri.to_string());
}
}
self.logged_items.push(log_item);
match self.error_behavior {
ErrorBehavior::StopOnFirstError => Err(err),
ErrorBehavior::ContinueWhenPossible => Ok(err),
}
}
pub fn filter_errors(&self) -> impl Iterator<Item = &LogItem> {
self.logged_items()
.iter()
.filter(|item| item.err_val.is_some())
}
pub fn has_status(&self, val: &str) -> bool {
self.logged_items().iter().any(|vi| {
if let Some(vs) = &vi.validation_status {
vs == val
} else {
false
}
})
}
pub fn has_error<E: Debug>(&self, err: E) -> bool {
let err_type = format!("{:?}", &err);
self.logged_items().iter().any(|vi| {
if let Some(e) = &vi.err_val {
e == &err_type
} else {
false
}
})
}
pub fn has_any_error(&self) -> bool {
self.filter_errors().next().is_some()
}
pub fn push_ingredient_uri<S: Into<String>>(&mut self, uri: S) {
self.ingredient_uris.push(uri.into());
}
pub fn pop_ingredient_uri(&mut self) -> Option<String> {
self.ingredient_uris.pop()
}
pub fn ingredient_uri(&self) -> Option<&str> {
self.ingredient_uris.last().map(|s| s.as_str())
}
pub fn push_current_uri<S: Into<String>>(&mut self, uri: S) {
self.current_uri.push(uri.into());
}
pub fn pop_current_uri(&mut self) -> Option<String> {
self.current_uri.pop()
}
pub fn current_uri(&self) -> Option<&str> {
self.current_uri.last().map(|s| s.as_str())
}
}
#[derive(Debug, Default, Eq, PartialEq)]
pub enum ErrorBehavior {
StopOnFirstError,
#[default]
ContinueWhenPossible,
}
mod log_item;
pub use log_item::{LogItem, LogKind};
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used)]
#![allow(clippy::unwrap_used)]
use std::fmt::{self, Display, Formatter};
mod detailed {
use super::SampleError;
use crate::{log_item, status_tracker::StatusTracker};
#[test]
fn aggregates_errors() {
let mut tracker = StatusTracker::default();
log_item!("test1", "test item 1", "test func").success(&mut tracker);
log_item!("test2", "test item 1", "test func")
.validation_status("foo.bar")
.failure(&mut tracker, SampleError {})
.unwrap();
dbg!(&tracker);
assert_eq!(tracker.logged_items().len(), 2);
assert!(tracker.has_status("foo.bar"));
assert!(!tracker.has_status("blah"));
assert!(tracker.has_error(SampleError {}));
assert!(!tracker.has_error("Something Else"));
let errors: Vec<&crate::status_tracker::LogItem> = tracker.filter_errors().collect();
assert_eq!(errors.len(), 1);
assert_eq!(tracker.logged_items().len(), 2);
}
#[test]
fn append() {
let mut tracker1 = StatusTracker::default();
let mut tracker2 = StatusTracker::default();
log_item!("test1", "test item 1", "test func").success(&mut tracker1);
log_item!("test2", "test item 1", "test func")
.failure(&mut tracker2, SampleError {})
.unwrap();
assert_eq!(tracker1.logged_items().len(), 1);
assert_eq!(tracker2.logged_items().len(), 1);
tracker1.append(&tracker2);
assert_eq!(tracker1.logged_items().len(), 2);
assert_eq!(tracker2.logged_items().len(), 1);
}
}
mod one_shot {
use super::SampleError;
use crate::{
log_item,
status_tracker::{ErrorBehavior, StatusTracker},
};
#[test]
fn stops_on_first_error() {
let mut tracker = StatusTracker::with_error_behavior(ErrorBehavior::StopOnFirstError);
log_item!("test1", "test item 1", "test func").success(&mut tracker);
let err = log_item!("test2", "test item 2 from macro", "test func")
.failure(&mut tracker, SampleError {})
.unwrap_err();
assert_eq!(err, SampleError {});
assert_eq!(tracker.logged_items().len(), 2);
}
}
#[derive(Debug, Eq, PartialEq)]
struct SampleError {}
impl Display for SampleError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "SampleError")
}
}
}