1use sea_orm::sea_query::IntoValueTuple;
2use sea_orm::{
3 ConnectionTrait, DbErr, EntityTrait, FromQueryResult, IntoIdentity, QuerySelect, Select,
4};
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Deserialize)]
13#[serde(default)]
14pub struct PageParams {
15 pub page: u64,
16 pub per_page: u64,
17}
18
19impl Default for PageParams {
20 fn default() -> Self {
21 Self {
22 page: 1,
23 per_page: 20,
24 }
25 }
26}
27
28impl PageParams {
29 fn clamped(&self) -> (u64, u64) {
30 (self.page.max(1), self.per_page.clamp(1, 100))
31 }
32}
33
34#[derive(Debug, Clone, Serialize)]
36pub struct PageResult<T> {
37 pub data: Vec<T>,
38 pub page: u64,
39 pub per_page: u64,
40 pub has_next: bool,
41 pub has_prev: bool,
42}
43
44impl<T> PageResult<T> {
45 pub fn map<U>(self, f: impl FnMut(T) -> U) -> PageResult<U> {
47 PageResult {
48 data: self.data.into_iter().map(f).collect(),
49 page: self.page,
50 per_page: self.per_page,
51 has_next: self.has_next,
52 has_prev: self.has_prev,
53 }
54 }
55}
56
57pub async fn paginate<E, M>(
61 query: Select<E>,
62 db: &impl ConnectionTrait,
63 params: &PageParams,
64) -> Result<PageResult<M>, DbErr>
65where
66 E: EntityTrait<Model = M>,
67 M: FromQueryResult + Send + Sync,
68{
69 let (page, per_page) = params.clamped();
70 let offset = (page - 1) * per_page;
71
72 let mut rows = query.offset(offset).limit(per_page + 1).all(db).await?;
73
74 let has_next = rows.len() as u64 > per_page;
75 if has_next {
76 rows.truncate(per_page as usize);
77 }
78
79 Ok(PageResult {
80 data: rows,
81 page,
82 per_page,
83 has_next,
84 has_prev: page > 1,
85 })
86}
87
88#[derive(Debug, Clone, Deserialize)]
98pub struct CursorParams<V = String> {
99 #[serde(default)]
100 pub per_page: Option<u64>,
101 #[serde(default)]
102 pub after: Option<V>,
103 #[serde(default)]
104 pub before: Option<V>,
105}
106
107impl<V> Default for CursorParams<V> {
108 fn default() -> Self {
109 Self {
110 per_page: None,
111 after: None,
112 before: None,
113 }
114 }
115}
116
117impl<V> CursorParams<V> {
118 fn clamped_per_page(&self) -> u64 {
119 self.per_page.unwrap_or(20).clamp(1, 100)
120 }
121}
122
123#[derive(Debug, Clone, Serialize)]
129pub struct CursorResult<T> {
130 pub data: Vec<T>,
131 pub per_page: u64,
132 pub next_cursor: Option<String>,
133 pub prev_cursor: Option<String>,
134 pub has_next: bool,
135 pub has_prev: bool,
136}
137
138impl<T> CursorResult<T> {
139 pub fn map<U>(self, f: impl FnMut(T) -> U) -> CursorResult<U> {
141 CursorResult {
142 data: self.data.into_iter().map(f).collect(),
143 per_page: self.per_page,
144 next_cursor: self.next_cursor,
145 prev_cursor: self.prev_cursor,
146 has_next: self.has_next,
147 has_prev: self.has_prev,
148 }
149 }
150}
151
152pub async fn paginate_cursor<E, M, C, V, F>(
164 query: Select<E>,
165 cursor_column: C,
166 cursor_fn: F,
167 db: &impl ConnectionTrait,
168 params: &CursorParams<V>,
169) -> Result<CursorResult<M>, DbErr>
170where
171 E: EntityTrait<Model = M>,
172 M: FromQueryResult + Send + Sync,
173 C: IntoIdentity,
174 V: IntoValueTuple + Clone,
175 F: Fn(&M) -> String,
176{
177 let per_page = params.clamped_per_page();
178 let mut cursor = query.cursor_by(cursor_column);
179
180 let is_backward = params.after.is_none() && params.before.is_some();
182
183 if let Some(ref after) = params.after {
184 cursor.after(after.clone());
185 cursor.first(per_page + 1);
186 } else if let Some(ref before) = params.before {
187 cursor.before(before.clone());
188 cursor.last(per_page + 1);
189 } else {
190 cursor.first(per_page + 1);
191 }
192
193 let mut rows = cursor.all(db).await?;
194
195 let (has_next, has_prev);
196
197 if is_backward {
198 has_prev = rows.len() as u64 > per_page;
199 has_next = !rows.is_empty();
200 if has_prev {
201 rows.remove(0);
202 }
203 } else {
204 has_next = rows.len() as u64 > per_page;
205 has_prev = params.after.is_some();
206 if has_next {
207 rows.truncate(per_page as usize);
208 }
209 }
210
211 let next_cursor = if has_next {
212 rows.last().map(&cursor_fn)
213 } else {
214 None
215 };
216 let prev_cursor = if has_prev {
217 rows.first().map(&cursor_fn)
218 } else {
219 None
220 };
221
222 Ok(CursorResult {
223 data: rows,
224 per_page,
225 next_cursor,
226 prev_cursor,
227 has_next,
228 has_prev,
229 })
230}