by_loco/model/query/paginate/mod.rs
1use sea_orm::{prelude::*, Condition, DatabaseConnection, EntityTrait, QueryFilter, SelectorTrait};
2use serde::{Deserialize, Serialize};
3
4/// Set the default pagination page size.
5const fn default_page_size() -> u64 {
6 25
7}
8
9/// Set the default pagination page.
10const fn default_page() -> u64 {
11 1
12}
13
14/// Structure representing the pagination query parameters.
15/// This struct allows to get the struct parameters from the query parameters.
16///
17/// # Example
18///
19/// ```
20/// use serde::{Deserialize, Serialize};
21/// use loco_rs::prelude::model::*;
22///
23/// #[derive(Debug, Deserialize)]
24/// pub struct ListQueryParams {
25/// pub title: Option<String>,
26/// pub content: Option<String>,
27/// #[serde(flatten)]
28/// pub pagination: query::PaginationQuery,
29/// }
30/// ````
31#[derive(Debug, Deserialize, Serialize)]
32pub struct PaginationQuery {
33 #[serde(
34 default = "default_page_size",
35 rename = "page_size",
36 deserialize_with = "deserialize_pagination_filter"
37 )]
38 pub page_size: u64,
39 #[serde(
40 default = "default_page",
41 rename = "page",
42 deserialize_with = "deserialize_pagination_filter"
43 )]
44 pub page: u64,
45}
46
47impl PaginationQuery {
48 #[must_use]
49 pub fn page(page: u64) -> Self {
50 Self {
51 page,
52 ..Default::default()
53 }
54 }
55}
56
57/// Default implementation for `PaginationQuery`.
58impl Default for PaginationQuery {
59 fn default() -> Self {
60 Self {
61 page_size: default_page_size(),
62 page: default_page(),
63 }
64 }
65}
66
67/// Deserialize pagination filter from string to u64 following a bug in
68/// `serde_urlencoded`.
69fn deserialize_pagination_filter<'de, D>(deserializer: D) -> Result<u64, D::Error>
70where
71 D: serde::Deserializer<'de>,
72{
73 let s: String = Deserialize::deserialize(deserializer)?;
74 s.parse().map_err(serde::de::Error::custom)
75}
76
77#[derive(Debug)]
78pub struct PageResponse<T> {
79 pub page: Vec<T>,
80 pub total_pages: u64,
81 pub total_items: u64,
82}
83
84use crate::Result as LocoResult;
85
86/// Paginate function for fetching paginated data from the database.
87///
88/// # Examples
89///
90/// Without conditions
91/// ```
92/// use loco_rs::tests_cfg::db;
93/// use sea_orm::{EntityTrait, QueryFilter, QuerySelect, QueryTrait};
94/// use loco_rs::prelude::*;
95///
96/// async fn example() {
97/// let db = db::dummy_connection().await;
98/// let pagination_query = query::PaginationQuery {
99/// page_size: 100,
100/// page: 1,
101/// };
102///
103/// let res = query::paginate(&db, db::test_db::Entity::find(), None, &pagination_query).await;
104/// }
105/// ````
106/// With conditions
107/// ```
108/// use loco_rs::tests_cfg::db;
109/// use sea_orm::{EntityTrait, QueryFilter, QuerySelect, QueryTrait};
110/// use loco_rs::prelude::*;
111///
112/// async fn example() {
113/// let db = db::dummy_connection().await;
114/// let pagination_query = query::PaginationQuery {
115/// page_size: 100,
116/// page: 1,
117/// };
118/// let condition = query::condition().contains(db::test_db::Column::Name, "loco").build();
119/// let res = query::paginate(&db, db::test_db::Entity::find(), Some(condition), &pagination_query).await;
120/// }
121/// ````
122/// With Order By
123/// ```
124/// use loco_rs::tests_cfg::db;
125/// use sea_orm::{EntityTrait, QueryFilter, QuerySelect, QueryTrait, sea_query::Order, QueryOrder};
126/// use loco_rs::prelude::*;
127///
128/// async fn example() {
129/// let db = db::dummy_connection().await;
130/// let pagination_query = query::PaginationQuery {
131/// page_size: 100,
132/// page: 1,
133/// };
134///
135/// let condition = query::condition().contains(db::test_db::Column::Name, "loco").build();
136/// let entity = db::test_db::Entity::find().order_by(db::test_db::Column::Name, Order::Desc);
137/// let res = query::paginate(&db, entity, Some(condition), &pagination_query).await;
138/// }
139/// ````
140///
141/// # Errors
142///
143/// Returns a `LocoResult` indicating any errors that occur
144/// during pagination.
145pub async fn paginate<E>(
146 db: &DatabaseConnection,
147 entity: Select<E>,
148 condition: Option<Condition>,
149 pagination_query: &PaginationQuery,
150) -> LocoResult<PageResponse<E::Model>>
151where
152 E: EntityTrait,
153 <E as EntityTrait>::Model: Sync,
154{
155 let page = pagination_query.page.saturating_sub(1);
156 let entity = if let Some(condition) = condition {
157 entity.filter(condition)
158 } else {
159 entity
160 };
161
162 let query = entity.paginate(db, pagination_query.page_size);
163 let total_pages_and_items = query.num_items_and_pages().await?;
164 let page: Vec<<E as EntityTrait>::Model> = query.fetch_page(page).await?;
165
166 let paginated_response = PageResponse {
167 page,
168 total_pages: total_pages_and_items.number_of_pages,
169 total_items: total_pages_and_items.number_of_items,
170 };
171
172 Ok(paginated_response)
173}
174
175/// Fetching a page from a selector.
176///
177/// # Examples
178///
179/// From Entity
180/// ```
181/// use loco_rs::tests_cfg::db;
182/// use sea_orm::{EntityTrait, QueryFilter, QuerySelect, QueryTrait};
183/// use loco_rs::prelude::*;
184///
185/// async fn example() {
186/// let db = db::dummy_connection().await;
187/// let pagination_query = query::PaginationQuery {
188/// page_size: 100,
189/// page: 1,
190/// };
191/// let res = query::fetch_page(&db, db::test_db::Entity::find(), &query::PaginationQuery::page(2)).await;
192/// }
193/// ``````
194///
195/// # Errors
196///
197/// Returns a `LocoResult` indicating any errors that occur
198/// during the fetch.
199pub async fn fetch_page<'db, C, S>(
200 db: &'db C,
201 selector: S,
202 pagination_query: &PaginationQuery,
203) -> LocoResult<PageResponse<<<S as PaginatorTrait<'db, C>>::Selector as SelectorTrait>::Item>>
204where
205 C: ConnectionTrait + Sync,
206 S: PaginatorTrait<'db, C> + Send,
207{
208 let page = pagination_query.page.saturating_sub(1);
209
210 let query = selector.paginate(db, pagination_query.page_size);
211 let total_pages_and_items = query.num_items_and_pages().await?;
212 let page = query.fetch_page(page).await?;
213
214 Ok(PageResponse {
215 page,
216 total_pages: total_pages_and_items.number_of_pages,
217 total_items: total_pages_and_items.number_of_items,
218 })
219}