use std::fmt::Debug;
pub trait Cursored {
type CursorType: Debug + Clone + Into<sea_query::SimpleExpr>;
fn cursor_value(&self) -> Self::CursorType;
fn cursor_column() -> sea_query::DynIden;
}
pub trait CursoredFilter {
type Entity: Cursored;
fn cursor(&self) -> Option<<Self::Entity as Cursored>::CursorType>;
fn set_cursor(&mut self, cursor: <Self::Entity as Cursored>::CursorType);
fn page_limit(&self) -> usize;
fn cursor_column_order() -> sea_query::Order {
sea_query::Order::Asc
}
}
#[derive(Debug, Clone)]
pub struct Paginated<T: Cursored> {
pub entries: Vec<T>,
pub next_cursor: Option<<T as Cursored>::CursorType>,
pub limit: usize,
}
impl<T: Cursored> Default for Paginated<T> {
fn default() -> Self {
Self {
entries: Vec::default(),
next_cursor: None,
limit: 10,
}
}
}
impl<T> Paginated<T>
where
T: Cursored,
{
pub fn new(entries: Vec<T>, limit: usize) -> Self {
let next_cursor = if entries.len() >= limit {
entries.last().map(|e| e.cursor_value())
} else {
None
};
Self {
entries,
next_cursor,
limit,
}
}
pub fn has_next(&self) -> bool {
self.next_cursor.is_some()
}
}
#[cfg(test)]
mod tests {
use sea_query::{enum_def, IntoIden};
use super::*;
#[enum_def]
struct Dummy {
id: i64,
}
impl Cursored for Dummy {
type CursorType = i64;
fn cursor_value(&self) -> Self::CursorType {
self.id
}
fn cursor_column() -> sea_query::DynIden {
DummyIden::Id.into_iden()
}
}
fn entries() -> Vec<Dummy> {
vec![
Dummy { id: 1 },
Dummy { id: 2 },
Dummy { id: 3 },
Dummy { id: 4 },
Dummy { id: 5 },
Dummy { id: 6 },
Dummy { id: 7 },
Dummy { id: 8 },
Dummy { id: 9 },
Dummy { id: 10 },
]
}
#[test]
fn cursor_set_when_enough_entries() {
let p = Paginated::new(entries(), 10);
assert_eq!(p.next_cursor, Some(10));
let p = Paginated::new(entries().into_iter().take(2).collect(), 2);
assert_eq!(p.next_cursor, Some(2));
}
#[test]
fn cursor_none_when_not_enough_entries() {
let entries = entries();
let num = entries.len() + 1;
let p = Paginated::new(entries, num);
assert_eq!(p.next_cursor, None);
}
}