1pub mod filters;
4
5use std::collections::BTreeSet;
6
7use crate::models::entry::Entries;
8
9#[derive(Debug, Clone, Copy)]
13pub struct FilterRunner;
14
15impl FilterRunner {
16 pub fn run<F>(filter_type: F, entries: &mut Entries)
25 where
26 F: Into<FilterType>,
27 {
28 let filter_type: FilterType = filter_type.into();
29
30 match filter_type {
31 FilterType::Title { query, operator } => {
32 Self::filter_by_title(&query, operator, entries);
33 }
34 FilterType::Author { query, operator } => {
35 Self::filter_by_author(&query, operator, entries);
36 }
37 FilterType::Tags { query, operator } => {
38 Self::filter_by_tags(&query, operator, entries);
39 }
40 }
41
42 filters::contains_no_annotations(entries);
44 }
45
46 fn filter_by_title(query: &[String], operator: FilterOperator, entries: &mut Entries) {
57 match operator {
58 FilterOperator::Any => filters::by_title_any(query, entries),
59 FilterOperator::All => filters::by_title_all(query, entries),
60 FilterOperator::Exact => filters::by_title_exact(&query.join(" "), entries),
61 }
62 }
63
64 fn filter_by_author(query: &[String], operator: FilterOperator, entries: &mut Entries) {
75 match operator {
76 FilterOperator::Any => filters::by_author_any(query, entries),
77 FilterOperator::All => filters::by_author_all(query, entries),
78 FilterOperator::Exact => filters::by_author_exact(&query.join(" "), entries),
79 }
80 }
81
82 fn filter_by_tags(query: &[String], operator: FilterOperator, entries: &mut Entries) {
93 let tags = BTreeSet::from_iter(query);
94
95 match operator {
96 FilterOperator::Any => filters::by_tags_any(&tags, entries),
97 FilterOperator::All => filters::by_tags_all(&tags, entries),
98 FilterOperator::Exact => filters::by_tags_exact(&tags, entries),
99 }
100 }
101}
102
103#[derive(Debug, Clone)]
108pub enum FilterType {
109 Title {
113 #[allow(missing_docs)]
114 query: Vec<String>,
115 #[allow(missing_docs)]
116 operator: FilterOperator,
117 },
118
119 Author {
123 #[allow(missing_docs)]
124 query: Vec<String>,
125 #[allow(missing_docs)]
126 operator: FilterOperator,
127 },
128
129 Tags {
133 #[allow(missing_docs)]
134 query: Vec<String>,
135 #[allow(missing_docs)]
136 operator: FilterOperator,
137 },
138}
139
140#[cfg(test)]
141impl FilterType {
142 fn title(query: &[&str], operator: FilterOperator) -> Self {
143 Self::Title {
144 query: query.iter().map(std::string::ToString::to_string).collect(),
145 operator,
146 }
147 }
148
149 fn author(query: &[&str], operator: FilterOperator) -> Self {
150 Self::Author {
151 query: query.iter().map(std::string::ToString::to_string).collect(),
152 operator,
153 }
154 }
155
156 fn tags(query: &[&str], operator: FilterOperator) -> Self {
157 Self::Tags {
158 query: query.iter().map(std::string::ToString::to_string).collect(),
159 operator,
160 }
161 }
162}
163
164#[derive(Debug, Clone, Copy, Default)]
168pub enum FilterOperator {
169 #[default]
171 Any,
172
173 All,
175
176 Exact,
178}
179
180#[cfg(test)]
181mod test {
182
183 use super::*;
184
185 use std::collections::HashMap;
186
187 use crate::models::annotation::Annotation;
188 use crate::models::book::Book;
189 use crate::models::entry::Entry;
190
191 fn create_test_entries() -> Entries {
192 let annotations = vec![
193 Annotation {
194 tags: create_test_tags(&["#tag01"]),
195 ..Default::default()
196 },
197 Annotation {
198 tags: create_test_tags(&["#tag02"]),
199 ..Default::default()
200 },
201 Annotation {
202 tags: create_test_tags(&["#tag03"]),
203 ..Default::default()
204 },
205 Annotation {
206 tags: create_test_tags(&["#tag01", "#tag02", "#tag03"]),
207 ..Default::default()
208 },
209 ];
210
211 let entry_00 = Entry {
212 book: Book {
213 title: "Incididunt Sint".to_string(),
214 author: "Quis Sint".to_string(),
215 ..Default::default()
216 },
217 annotations: annotations.clone(),
218 };
219
220 let entry_01 = Entry {
223 book: Book {
224 title: "Laboris Ex Cillum".to_string(),
225 author: "Lorem Du Quis".to_string(),
226 ..Default::default()
227 },
228 annotations,
229 };
230
231 let mut data = HashMap::new();
232 data.insert("00".to_string(), entry_00);
233 data.insert("01".to_string(), entry_01);
234
235 data
236 }
237
238 fn create_test_tags(tags: &[&str]) -> BTreeSet<String> {
239 tags.iter().map(std::string::ToString::to_string).collect()
240 }
241
242 #[test]
244 fn title_any() {
245 let mut entries = create_test_entries();
246
247 FilterRunner::run(
248 FilterType::title(&["incididunt", "laboris"], FilterOperator::Any),
249 &mut entries,
250 );
251
252 let annotations = entries
253 .values()
254 .flat_map(|entry| &entry.annotations)
255 .count();
256
257 assert_eq!(entries.len(), 2);
258 assert_eq!(annotations, 8);
259 }
260
261 #[test]
263 fn title_all() {
264 let mut entries = create_test_entries();
265
266 FilterRunner::run(
267 FilterType::title(&["laboris", "cillum"], FilterOperator::All),
268 &mut entries,
269 );
270
271 let annotations = entries
272 .values()
273 .flat_map(|entry| &entry.annotations)
274 .count();
275
276 assert_eq!(entries.len(), 1);
277 assert_eq!(annotations, 4);
278 }
279
280 #[test]
282 fn title_exact() {
283 let mut entries = create_test_entries();
284
285 FilterRunner::run(
286 FilterType::title(&["incididunt", "sint"], FilterOperator::Exact),
287 &mut entries,
288 );
289
290 let annotations = entries
291 .values()
292 .flat_map(|entry| &entry.annotations)
293 .count();
294
295 assert_eq!(entries.len(), 1);
296 assert_eq!(annotations, 4);
297 }
298
299 #[test]
301 fn author_any() {
302 let mut entries = create_test_entries();
303
304 FilterRunner::run(
305 FilterType::author(&["quis"], FilterOperator::Any),
306 &mut entries,
307 );
308
309 let annotations = entries
310 .values()
311 .flat_map(|entry| &entry.annotations)
312 .count();
313
314 assert_eq!(entries.len(), 2);
315 assert_eq!(annotations, 8);
316 }
317
318 #[test]
320 fn author_all() {
321 let mut entries = create_test_entries();
322
323 FilterRunner::run(
324 FilterType::author(&["lorem", "sint"], FilterOperator::All),
325 &mut entries,
326 );
327
328 let annotations = entries
329 .values()
330 .flat_map(|entry| &entry.annotations)
331 .count();
332
333 assert_eq!(entries.len(), 0);
334 assert_eq!(annotations, 0);
335 }
336
337 #[test]
339 fn author_exact() {
340 let mut entries = create_test_entries();
341
342 FilterRunner::run(
343 FilterType::author(&["lorem", "du", "quis"], FilterOperator::Exact),
344 &mut entries,
345 );
346
347 let annotations = entries
348 .values()
349 .flat_map(|entry| &entry.annotations)
350 .count();
351
352 assert_eq!(entries.len(), 1);
353 assert_eq!(annotations, 4);
354 }
355
356 #[test]
358 fn tags_any() {
359 let mut entries = create_test_entries();
360
361 FilterRunner::run(
362 FilterType::tags(&["#tag01", "#tag03"], FilterOperator::Any),
363 &mut entries,
364 );
365
366 let annotations = entries
367 .values()
368 .flat_map(|entry| &entry.annotations)
369 .count();
370
371 assert_eq!(entries.len(), 2);
372 assert_eq!(annotations, 6);
373 }
374
375 #[test]
377 fn tags_all() {
378 let mut entries = create_test_entries();
379
380 FilterRunner::run(
381 FilterType::tags(&["#tag01", "#tag03"], FilterOperator::All),
382 &mut entries,
383 );
384
385 let annotations = entries
386 .values()
387 .flat_map(|entry| &entry.annotations)
388 .count();
389
390 assert_eq!(entries.len(), 2);
391 assert_eq!(annotations, 2);
392 }
393
394 #[test]
396 fn tags_exact() {
397 let mut entries = create_test_entries();
398
399 FilterRunner::run(
400 FilterType::tags(&["#tag01", "#tag02", "#tag03"], FilterOperator::Exact),
401 &mut entries,
402 );
403
404 let annotations = entries
405 .values()
406 .flat_map(|entry| &entry.annotations)
407 .count();
408
409 assert_eq!(entries.len(), 2);
410 assert_eq!(annotations, 2);
411 }
412
413 #[test]
415 fn tags_exact_different_order() {
416 let mut entries = create_test_entries();
417
418 FilterRunner::run(
419 FilterType::tags(&["#tag03", "#tag02", "#tag01"], FilterOperator::Exact),
420 &mut entries,
421 );
422
423 let annotations = entries
424 .values()
425 .flat_map(|entry| &entry.annotations)
426 .count();
427
428 assert_eq!(entries.len(), 2);
429 assert_eq!(annotations, 2);
430 }
431
432 #[test]
434 fn multi() {
435 let mut entries = create_test_entries();
436
437 FilterRunner::run(
438 FilterType::title(&["sint"], FilterOperator::Any),
439 &mut entries,
440 );
441
442 FilterRunner::run(
443 FilterType::author(&["quis", "sint"], FilterOperator::Exact),
444 &mut entries,
445 );
446
447 FilterRunner::run(
448 FilterType::tags(&["#tag02"], FilterOperator::Any),
449 &mut entries,
450 );
451
452 let annotations = entries
453 .values()
454 .flat_map(|entry| &entry.annotations)
455 .count();
456
457 assert_eq!(entries.len(), 1);
458 assert_eq!(annotations, 2);
459 }
460}