hexser_potions/crud/
mod.rs

1//! CRUD potions: a minimal in-memory repository for a single entity type.
2//!
3//! Demonstrates implementing the `Repository<T>` trait with a simple adapter
4//! and using it from application code.
5//!
6//! Revision History
7//! - 2025-10-07T11:57:00Z @AI: Migrate to v0.4 Repository/QueryRepository; remove id-centric methods; update API usage.
8
9use hexser::prelude::*;
10
11#[derive(Clone, Debug, PartialEq)]
12pub struct Item {
13    pub id: u64,
14    pub name: String,
15}
16
17impl Entity for Item {
18    type Id = u64;
19}
20
21pub trait ItemRepository: Repository<Item> {}
22
23#[derive(Default)]
24pub struct InMemoryItemRepository {
25    pub items: Vec<Item>,
26}
27
28impl Repository<Item> for InMemoryItemRepository {
29    fn save(&mut self, entity: Item) -> HexResult<()> {
30        if let Some(existing) = self.items.iter_mut().find(|e| e.id == entity.id) {
31            *existing = entity;
32        } else {
33            self.items.push(entity);
34        }
35        Ok(())
36    }
37}
38
39#[derive(Clone, Debug, PartialEq, Eq)]
40pub enum ItemFilter { All, ById(u64) }
41#[derive(Clone, Copy, Debug, PartialEq, Eq)]
42pub enum ItemSortKey { Id }
43
44impl hexser::ports::repository::QueryRepository<Item> for InMemoryItemRepository {
45    type Filter = ItemFilter;
46    type SortKey = ItemSortKey;
47
48    fn find_one(&self, filter: &ItemFilter) -> HexResult<Option<Item>> {
49        let found = match filter {
50            ItemFilter::All => self.items.first().cloned(),
51            ItemFilter::ById(id) => self.items.iter().find(|e| e.id == *id).cloned(),
52        };
53        Ok(found)
54    }
55
56    fn find(&self, filter: &ItemFilter, _opts: hexser::ports::repository::FindOptions<ItemSortKey>) -> HexResult<Vec<Item>> {
57        let items: Vec<Item> = match filter {
58            ItemFilter::All => self.items.clone(),
59            ItemFilter::ById(id) => self.items.iter().filter(|e| e.id == *id).cloned().collect(),
60        };
61        Ok(items)
62    }
63
64    fn delete_where(&mut self, filter: &ItemFilter) -> HexResult<u64> {
65        let before = self.items.len();
66        match filter {
67            ItemFilter::All => self.items.clear(),
68            ItemFilter::ById(id) => self.items.retain(|e| e.id != *id),
69        }
70        Ok((before.saturating_sub(self.items.len())) as u64)
71    }
72}
73
74impl ItemRepository for InMemoryItemRepository {}
75
76pub fn create<R>(repo: &mut R, id: u64, name: impl Into<String>) -> HexResult<Item>
77where
78    R: ItemRepository + hexser::ports::repository::QueryRepository<Item, Filter = ItemFilter>,
79{
80    let item = Item { id, name: name.into() };
81    // naive uniqueness check via QueryRepository
82    if <R as hexser::ports::repository::QueryRepository<Item>>::exists(repo, &ItemFilter::ById(id))? {
83        return Err(hexser::Hexserror::domain("E_HEXSER_POTIONS_ID_TAKEN", "ID already exists"));
84    }
85    repo.save(item.clone())?;
86    Ok(item)
87}
88
89pub fn get<R>(repo: &R, id: u64) -> HexResult<Item>
90where
91    R: ItemRepository + hexser::ports::repository::QueryRepository<Item, Filter = ItemFilter>,
92{
93    <R as hexser::ports::repository::QueryRepository<Item>>::find_one(repo, &ItemFilter::ById(id))?
94        .ok_or_else(|| hexser::Hexserror::not_found("Item", &id.to_string()))
95}
96
97pub fn delete<R>(repo: &mut R, id: u64) -> HexResult<()>
98where
99    R: ItemRepository + hexser::ports::repository::QueryRepository<Item, Filter = ItemFilter>,
100{
101    let _ = <R as hexser::ports::repository::QueryRepository<Item>>::delete_where(repo, &ItemFilter::ById(id))?;
102    Ok(())
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn crud_flow() {
111        let mut repo = InMemoryItemRepository::default();
112        let a = create(&mut repo, 1, "A").unwrap();
113        assert_eq!(a.name, "A");
114        let fetched = get(&repo, 1).unwrap();
115        assert_eq!(fetched, a);
116        delete(&mut repo, 1).unwrap();
117        assert!(get(&repo, 1).is_err());
118    }
119}