feat: return a vector of boxed error from catalog.for_each_mut

execution is not interrupted and user can raise any error implementing
Error trait
This commit is contained in:
2025-11-12 07:53:41 +01:00
parent 222864389c
commit da74060d51
3 changed files with 71 additions and 4 deletions

View File

@@ -24,17 +24,25 @@ impl Catalog {
F: FnMut(&mut T) -> Result<(), Box<dyn error::Error>>, F: FnMut(&mut T) -> Result<(), Box<dyn error::Error>>,
{ {
self.on_items(|items| { self.on_items(|items| {
let mut errors: Vec<Box<dyn error::Error>> = Vec::new();
for entity in items.values_mut() { for entity in items.values_mut() {
let original_item = let original_item =
T::try_from(entity.clone()).map_err(|_| FailedTo::ConvertEntity)?; T::try_from(entity.clone()).map_err(|_| FailedTo::ConvertEntity)?;
let mut item = T::try_from(entity.clone()).map_err(|_| FailedTo::ConvertEntity)?; let mut item = original_item.clone();
predicate(&mut item).map_err(FailedTo::ExecutePredicate)?; let result = predicate(&mut item);
if let Err(e) = result {
errors.push(e);
}
if item != original_item { if item != original_item {
*entity = T::try_into(item).map_err(|_| FailedTo::ConvertObject)?; *entity = T::try_into(item).map_err(|_| FailedTo::ConvertObject)?;
entity.state = EntityState::Updated; entity.state = EntityState::Updated;
} }
} }
if errors.is_empty() {
Ok(()) Ok(())
} else {
Err(FailedTo::ExecutePredicate(errors))
}
}) })
} }
} }

View File

@@ -12,7 +12,7 @@ pub enum FailedTo {
/// Failed to convert from Value to type. /// Failed to convert from Value to type.
ConvertValue, ConvertValue,
/// Failed to execute predicate to mutate an item. /// Failed to execute predicate to mutate an item.
ExecutePredicate(Box<dyn error::Error>), ExecutePredicate(Vec<Box<dyn error::Error>>),
/// Failed to initialize the database. /// Failed to initialize the database.
InitDatabase, InitDatabase,
/// Failed to load data from the database. /// Failed to load data from the database.

View File

@@ -1,6 +1,16 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::*; use crate::*;
#[derive(Debug, PartialEq)]
enum Test {
Error(String),
}
impl std::fmt::Display for Test {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
write!(f, "error!")
}
}
impl std::error::Error for Test {}
fn prepare_catalog(catalog: &mut Catalog) { fn prepare_catalog(catalog: &mut Catalog) {
let items = vec![ let items = vec![
Item { Item {
@@ -51,4 +61,53 @@ mod tests {
Ok(()) Ok(())
}); });
} }
#[test]
fn for_each_mut_should_handle_empty_catalog() {
let catalog = Catalog::new("test.db");
let result = catalog.for_each_mut(|_item: &mut Item| Ok(()));
assert!(result.is_ok());
let count = catalog.with_items(|items| Ok(items.len())).unwrap();
assert_eq!(count, 0);
}
#[test]
fn for_each_mut_should_handle_closure_error() {
let mut catalog = Catalog::new("test.db");
prepare_catalog(&mut catalog);
let result = catalog.for_each_mut(|item: &mut Item| {
if item.id == "2" {
Err(Box::new(Test::Error(item.id.clone())))
} else {
item.price += 50;
Ok(())
}
});
assert!(result.is_err());
if let Err(FailedTo::ExecutePredicate(e)) = result {
assert_eq!(
e[0].downcast_ref::<Test>(),
Some(&Test::Error("2".to_string()))
);
}
let item1: Item = catalog.get("1").unwrap().unwrap();
let item2: Item = catalog.get("2").unwrap().unwrap();
let item3: Item = catalog.get("3").unwrap().unwrap();
assert_eq!(item1.price, 150); // Modified
assert_eq!(item2.price, 200); // Not modified due to error
assert_eq!(item3.price, 350); // Modified
let _ = catalog.with_items(|items| {
let entity = items.get("1").unwrap();
assert_eq!(entity.state, EntityState::Updated);
Ok(())
});
let _ = catalog.with_items(|items| {
let entity = items.get("2").unwrap();
assert_eq!(entity.state, EntityState::New); // Should remain Unchanged
Ok(())
});
let _ = catalog.with_items(|items| {
let entity = items.get("3").unwrap();
assert_eq!(entity.state, EntityState::Updated);
Ok(())
});
}
} }