diff --git a/01.workspace/heave/get_by_finds_item.db b/01.workspace/heave/get_by_finds_item.db new file mode 100644 index 0000000..88cc83d Binary files /dev/null and b/01.workspace/heave/get_by_finds_item.db differ diff --git a/01.workspace/heave/get_by_multiple_matches.db b/01.workspace/heave/get_by_multiple_matches.db new file mode 100644 index 0000000..88cc83d Binary files /dev/null and b/01.workspace/heave/get_by_multiple_matches.db differ diff --git a/01.workspace/heave/get_by_on_empty_catalog_returns_none.db b/01.workspace/heave/get_by_on_empty_catalog_returns_none.db new file mode 100644 index 0000000..88cc83d Binary files /dev/null and b/01.workspace/heave/get_by_on_empty_catalog_returns_none.db differ diff --git a/01.workspace/heave/get_by_returns_none_when_no_match.db b/01.workspace/heave/get_by_returns_none_when_no_match.db new file mode 100644 index 0000000..88cc83d Binary files /dev/null and b/01.workspace/heave/get_by_returns_none_when_no_match.db differ diff --git a/01.workspace/heave/src/imp/catalog_get_by.rs b/01.workspace/heave/src/imp/catalog_get_by.rs new file mode 100644 index 0000000..91b8cdb --- /dev/null +++ b/01.workspace/heave/src/imp/catalog_get_by.rs @@ -0,0 +1,45 @@ +use crate::*; + +impl Catalog { + /// Retrieves the first entity from the in-memory catalog that satisfies a given predicate. + /// + /// This is a purely in-memory operation and does not interact with the database. + /// It iterates through all entities currently loaded in the catalog, attempts to + /// convert each to the specified type `T`, and then applies the provided + /// `predicate` function. The first entity that successfully converts and + /// satisfies the predicate is returned. + /// + /// Entities that fail to convert to type `T` are silently skipped and do not + /// cause an error to be returned by this function. + /// + /// # Type Parameters + /// + /// * `T` - The target type to convert the entity into, which must implement the `EAV` trait. + /// * `F` - The type of the predicate closure, which takes a reference to `T` and returns a `bool`. + /// + /// # Arguments + /// + /// * `predicate` - A closure that defines the condition an entity must meet to be returned. + /// + /// # Returns + /// + /// An `Ok(Some(T))` containing the first converted entity that satisfies the predicate, + /// or `Ok(None)` if no such entity is found or if all matching entities fail conversion. + /// + /// # Errors + /// + /// Returns `Err(FailedTo)` if an internal error occurs during the `with_items` operation + /// (e.g., mutex poisoning). + pub fn get_by(&self, predicate: F) -> Result, FailedTo> + where + T: EAV, + F: Fn(&T) -> bool, + { + self.with_items(|items| { + Ok(items + .values() + .flat_map(|entity| T::try_from(entity.clone())) + .find(|item| predicate(item))) + }) + } +} diff --git a/01.workspace/heave/src/imp/mod.rs b/01.workspace/heave/src/imp/mod.rs index a2585eb..af8b38a 100644 --- a/01.workspace/heave/src/imp/mod.rs +++ b/01.workspace/heave/src/imp/mod.rs @@ -2,6 +2,7 @@ pub mod bool_try_from_value; pub mod catalog_contains_key; pub mod catalog_delete; pub mod catalog_get; +pub mod catalog_get_by; pub mod catalog_init; pub mod catalog_insert_many; pub mod catalog_is_empty; diff --git a/01.workspace/heave/src/str/item.rs b/01.workspace/heave/src/str/item.rs index 19e66fc..1571e3b 100644 --- a/01.workspace/heave/src/str/item.rs +++ b/01.workspace/heave/src/str/item.rs @@ -29,7 +29,6 @@ impl From for Entity { let mut entity = Entity::new::() .with_id(&value.id) .with_ref_date(value.first_seen) - .with_subclass("subitem") .with_attribute("name", value.name) .with_attribute("price", value.price) .with_attribute("discount", value.discount) diff --git a/01.workspace/heave/src/tst/catalog_get_by.rs b/01.workspace/heave/src/tst/catalog_get_by.rs new file mode 100644 index 0000000..162b9d5 --- /dev/null +++ b/01.workspace/heave/src/tst/catalog_get_by.rs @@ -0,0 +1,80 @@ +#[cfg(test)] +mod tests { + use crate::*; + fn catalog(name: &str) -> Catalog { + let path = format!("{}.db", name); + let fs_path = path::Path::new(&path); + if fs_path.exists() { + std::fs::remove_file(fs_path).unwrap(); + } + let catalog = Catalog::new(&path); + catalog.init().unwrap(); + catalog + } + #[test] + fn get_by_finds_item() { + let mut catalog = catalog("get_by_finds_item"); + let item1 = Item { + id: "1".to_string(), + name: "one".to_string(), + ..Default::default() + }; + let item2 = Item { + id: "2".to_string(), + name: "two".to_string(), + ..Default::default() + }; + catalog + .insert_many(vec![item1.clone(), item2.clone()]) + .unwrap(); + let found = catalog + .get_by(|item: &Item| item.name == "two") + .unwrap() + .unwrap(); + assert_eq!(found, item2); + } + #[test] + fn get_by_returns_none_when_no_match() { + let mut catalog = catalog("get_by_returns_none_when_no_match"); + let item1 = Item { + id: "1".to_string(), + name: "one".to_string(), + ..Default::default() + }; + catalog.insert_many(vec![item1.clone()]).unwrap(); + let found = catalog.get_by(|item: &Item| item.name == "two").unwrap(); + assert!(found.is_none()); + } + #[test] + fn get_by_on_empty_catalog_returns_none() { + let catalog = catalog("get_by_on_empty_catalog_returns_none"); + let found = catalog.get_by(|item: &Item| item.name == "any").unwrap(); + assert!(found.is_none()); + } + #[test] + fn get_by_multiple_matches() { + let mut catalog = catalog("get_by_multiple_matches"); + let item1 = Item { + id: "1".to_string(), + name: "match".to_string(), + price: 10, + ..Default::default() + }; + let item2 = Item { + id: "2".to_string(), + name: "match".to_string(), + price: 20, + ..Default::default() + }; + catalog + .insert_many(vec![item1.clone(), item2.clone()]) + .unwrap(); + // `get_by` should return the first match it finds. The order is not guaranteed + // by the underlying HashMap, so we just check that it returns one of them. + let found = catalog + .get_by(|item: &Item| item.name == "match") + .unwrap() + .unwrap(); + assert!(found == item1 || found == item2); + } +} diff --git a/01.workspace/heave/src/tst/catalog_load_by_filter.rs b/01.workspace/heave/src/tst/catalog_load_by_filter.rs index ddd9209..90b1919 100644 --- a/01.workspace/heave/src/tst/catalog_load_by_filter.rs +++ b/01.workspace/heave/src/tst/catalog_load_by_filter.rs @@ -1462,7 +1462,7 @@ mod tests { }; let item4 = Item { id: "item-4".to_string(), - subclass: None, // -> "subitem" + subclass: Some("subitem".to_string()), ..Default::default() }; let another_item = AnotherItem { diff --git a/01.workspace/heave/src/tst/mod.rs b/01.workspace/heave/src/tst/mod.rs index bbb7e32..8f2e324 100644 --- a/01.workspace/heave/src/tst/mod.rs +++ b/01.workspace/heave/src/tst/mod.rs @@ -1,5 +1,6 @@ pub mod catalog_delete; pub mod catalog_get; +pub mod catalog_get_by; pub mod catalog_init; pub mod catalog_insert_many; pub mod catalog_integration;