#![allow(clippy::type_complexity)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SortDir {
Asc,
Desc,
}
#[derive(Debug, Clone)]
pub struct QueryResult<T> {
pub entries: Vec<T>,
pub total: usize,
pub page: usize,
pub page_size: usize,
pub groups: Option<std::collections::HashMap<String, Vec<T>>>,
}
pub struct Query<'a, T> {
records: &'a [T],
filter: Option<Box<dyn Fn(&T) -> bool + 'a>>,
search: Option<Box<dyn Fn(&T) -> bool + 'a>>,
sort: Option<Box<dyn Fn(&T, &T) -> std::cmp::Ordering + 'a>>,
group: Option<Box<dyn Fn(&T) -> String + 'a>>,
page: usize,
page_size: usize,
}
impl<'a, T: Clone> Query<'a, T> {
pub fn new(records: &'a [T]) -> Self {
Self {
records,
filter: None,
search: None,
sort: None,
group: None,
page: 1,
page_size: 25,
}
}
pub fn filter(mut self, f: impl Fn(&T) -> bool + 'a) -> Self {
self.filter = Some(Box::new(f));
self
}
pub fn search(mut self, query: &'a str, fields_fn: impl Fn(&T) -> Vec<&str> + 'a) -> Self {
let q = query.to_lowercase();
self.search = Some(Box::new(move |record: &T| {
fields_fn(record)
.iter()
.any(|field| field.to_lowercase().contains(&q))
}));
self
}
pub fn sort(mut self, f: impl Fn(&T, &T) -> std::cmp::Ordering + 'a) -> Self {
self.sort = Some(Box::new(f));
self
}
pub fn sort_by(
mut self,
specs: Vec<(SortDir, Box<dyn Fn(&T, &T) -> std::cmp::Ordering + 'a>)>,
) -> Self {
self.sort = Some(Box::new(move |a: &T, b: &T| {
for (dir, cmp_fn) in &specs {
let cmp = cmp_fn(a, b);
if cmp != std::cmp::Ordering::Equal {
return match dir {
SortDir::Asc => cmp,
SortDir::Desc => cmp.reverse(),
};
}
}
std::cmp::Ordering::Equal
}));
self
}
pub fn group(mut self, key_fn: impl Fn(&T) -> String + 'a) -> Self {
self.group = Some(Box::new(key_fn));
self
}
pub fn page(mut self, page: usize) -> Self {
self.page = page;
self
}
pub fn page_size(mut self, size: usize) -> Self {
self.page_size = size;
self
}
pub fn run(self) -> QueryResult<T> {
let mut results: Vec<T> = match &self.filter {
Some(f) => self.records.iter().filter(|r| f(r)).cloned().collect(),
None => self.records.to_vec(),
};
if let Some(search_fn) = &self.search {
results.retain(|r| search_fn(r));
}
if let Some(sort_fn) = &self.sort {
results.sort_by(|a, b| sort_fn(a, b));
}
let total = results.len();
let page = self.page.max(1);
let start = (page - 1) * self.page_size;
let entries = if start < total {
results[start..total.min(start + self.page_size)].to_vec()
} else {
Vec::new()
};
let groups = self.group.map(|key_fn| {
let mut map: std::collections::HashMap<String, Vec<T>> =
std::collections::HashMap::new();
for entry in &entries {
map.entry(key_fn(entry)).or_default().push(entry.clone());
}
map
});
QueryResult {
entries,
total,
page,
page_size: self.page_size,
groups,
}
}
}