hexser/ports/
repository.rs1#[derive(Debug, Clone)]
17pub struct FindOptions<K> {
18 pub sort: Option<Vec<Sort<K>>>,
19 pub limit: Option<u32>,
20 pub offset: Option<u64>,
21}
22
23impl<K> Default for FindOptions<K> {
24 fn default() -> Self {
25 Self { sort: None, limit: None, offset: None }
26 }
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum Direction {
31 Asc,
32 Desc,
33}
34
35#[derive(Debug, Clone)]
36pub struct Sort<K> {
37 pub key: K,
38 pub direction: Direction,
39}
40
41pub trait Repository<T>
52where
53 T: crate::domain::entity::Entity,
54{
55 fn save(&mut self, entity: T) -> crate::result::hex_result::HexResult<()>;
57}
58
59pub trait QueryRepository<T>
61where
62 T: crate::domain::entity::Entity,
63{
64 type Filter;
66
67 type SortKey;
69
70 fn find_one(&self, filter: &Self::Filter) -> crate::result::hex_result::HexResult<Option<T>>;
72
73 fn find(
75 &self,
76 filter: &Self::Filter,
77 options: FindOptions<Self::SortKey>,
78 ) -> crate::result::hex_result::HexResult<Vec<T>>;
79
80 fn exists(&self, filter: &Self::Filter) -> crate::result::hex_result::HexResult<bool> {
82 Ok(self.find_one(filter)?.is_some())
83 }
84
85 fn count(&self, filter: &Self::Filter) -> crate::result::hex_result::HexResult<u64> {
87 Ok(self.find(filter, FindOptions::default())?.len() as u64)
88 }
89
90 fn delete_where(&mut self, _filter: &Self::Filter) -> crate::result::hex_result::HexResult<u64> {
92 Ok(0)
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 #[derive(Clone, Debug)]
103 struct TestEntity {
104 id: u64,
105 name: String,
106 }
107
108 impl crate::domain::entity::Entity for TestEntity {
109 type Id = u64;
110 }
111
112 #[derive(Clone, Debug)]
113 enum TestFilter {
114 ById(u64),
115 NameEquals(String),
116 All,
117 And(Vec<TestFilter>),
118 }
119
120 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
121 enum TestSortKey { Id, Name }
122
123 #[derive(Default)]
124 struct TestRepository {
125 entities: Vec<TestEntity>,
126 }
127
128 impl crate::ports::repository::Repository<TestEntity> for TestRepository {
129 fn save(&mut self, entity: TestEntity) -> crate::result::hex_result::HexResult<()> {
130 if let Some(i) = self.entities.iter().position(|e| e.id == entity.id) {
131 self.entities[i] = entity;
132 } else {
133 self.entities.push(entity);
134 }
135 Ok(())
136 }
137 }
138
139 impl crate::ports::repository::QueryRepository<TestEntity> for TestRepository {
140 type Filter = TestFilter;
141 type SortKey = TestSortKey;
142
143 fn find_one(&self, filter: &Self::Filter) -> crate::result::hex_result::HexResult<Option<TestEntity>> {
144 Ok(self.entities.iter().find(|e| matches_filter(e, filter)).cloned())
145 }
146
147 fn find(
148 &self,
149 filter: &Self::Filter,
150 options: crate::ports::repository::FindOptions<Self::SortKey>,
151 ) -> crate::result::hex_result::HexResult<Vec<TestEntity>> {
152 let mut items: Vec<_> = self
153 .entities
154 .iter()
155 .filter(|e| matches_filter(e, filter))
156 .cloned()
157 .collect();
158
159 if let Some(sorts) = options.sort {
160 for s in sorts.into_iter().rev() {
161 match (s.key, s.direction) {
162 (TestSortKey::Id, crate::ports::repository::Direction::Asc) => items.sort_by_key(|e| e.id),
163 (TestSortKey::Id, crate::ports::repository::Direction::Desc) => items.sort_by_key(|e| std::cmp::Reverse(e.id)),
164 (TestSortKey::Name, crate::ports::repository::Direction::Asc) => items.sort_by(|a,b| a.name.cmp(&b.name)),
165 (TestSortKey::Name, crate::ports::repository::Direction::Desc) => items.sort_by(|a,b| b.name.cmp(&a.name)),
166 }
167 }
168 }
169
170 let offset = options.offset.unwrap_or(0) as usize;
171 let limit = options.limit.map(|l| l as usize).unwrap_or_else(|| items.len().saturating_sub(offset));
172 let end = offset.saturating_add(limit).min(items.len());
173 Ok(items.into_iter().skip(offset).take(end.saturating_sub(offset)).collect())
174 }
175
176 fn delete_where(&mut self, filter: &Self::Filter) -> crate::result::hex_result::HexResult<u64> {
177 let before = self.entities.len();
178 self.entities.retain(|e| !matches_filter(e, filter));
179 Ok((before - self.entities.len()) as u64)
180 }
181 }
182
183 fn matches_filter(e: &TestEntity, f: &TestFilter) -> bool {
184 match f {
185 TestFilter::ById(id) => e.id == *id,
186 TestFilter::NameEquals(n) => &e.name == n,
187 TestFilter::All => true,
188 TestFilter::And(fs) => fs.iter().all(|x| matches_filter(e, x)),
189 }
190 }
191
192 #[test]
193 fn test_repository_save_and_find_new_api() {
194 let mut repo = TestRepository { entities: Vec::new() };
197 <TestRepository as crate::ports::repository::Repository<TestEntity>>::save(&mut repo, TestEntity { id: 2, name: String::from("B") }).unwrap();
198 <TestRepository as crate::ports::repository::Repository<TestEntity>>::save(&mut repo, TestEntity { id: 1, name: String::from("A") }).unwrap();
199
200 let found = <TestRepository as crate::ports::repository::QueryRepository<TestEntity>>::find_one(&repo, &TestFilter::ById(1)).unwrap();
202 assert!(found.is_some());
203
204 let opts = crate::ports::repository::FindOptions { sort: Some(vec![crate::ports::repository::Sort { key: TestSortKey::Name, direction: crate::ports::repository::Direction::Asc }]), limit: Some(1), offset: Some(0) };
206 let page = <TestRepository as crate::ports::repository::QueryRepository<TestEntity>>::find(&repo, &TestFilter::All, opts).unwrap();
207 assert_eq!(page.len(), 1);
208 assert_eq!(page[0].name, "A");
209
210 let removed = <TestRepository as crate::ports::repository::QueryRepository<TestEntity>>::delete_where(&mut repo, &TestFilter::ById(2)).unwrap();
212 assert_eq!(removed, 1);
213 let none = <TestRepository as crate::ports::repository::QueryRepository<TestEntity>>::find_one(&repo, &TestFilter::ById(2)).unwrap();
214 assert!(none.is_none());
215 }
216}