karbon_framework/db/
pagination.rs1use serde::{Deserialize, Deserializer, Serialize};
2
3#[derive(Debug, Clone, Deserialize)]
5pub struct PaginationParams {
6 #[serde(default = "default_page", deserialize_with = "string_or_u32")]
7 pub page: u32,
8 #[serde(default = "default_per_page", deserialize_with = "string_or_u32", alias = "limit")]
9 pub per_page: u32,
10 #[serde(default)]
11 pub sort: Option<String>,
12 #[serde(default = "default_order")]
13 pub order: String,
14 #[serde(default)]
15 pub search: Option<String>,
16}
17
18fn string_or_u32<'de, D>(deserializer: D) -> Result<u32, D::Error>
20where
21 D: Deserializer<'de>,
22{
23 #[derive(Deserialize)]
24 #[serde(untagged)]
25 enum StringOrU32 {
26 Num(u32),
27 Str(String),
28 }
29
30 match StringOrU32::deserialize(deserializer)? {
31 StringOrU32::Num(n) => Ok(n),
32 StringOrU32::Str(s) => s.parse::<u32>().map_err(serde::de::Error::custom),
33 }
34}
35
36fn default_page() -> u32 {
37 1
38}
39fn default_per_page() -> u32 {
40 20
41}
42fn default_order() -> String {
43 "desc".to_string()
44}
45
46const MAX_PER_PAGE: u32 = 200;
48
49impl PaginationParams {
50 pub fn safe_per_page(&self) -> u32 {
52 self.per_page.min(MAX_PER_PAGE).max(1)
53 }
54
55 pub fn offset(&self) -> u32 {
57 (self.page.saturating_sub(1)) * self.safe_per_page()
58 }
59
60 pub fn order_direction(&self) -> &str {
62 match self.order.to_lowercase().as_str() {
63 "asc" => "ASC",
64 _ => "DESC",
65 }
66 }
67
68 pub fn sort_column<'a>(&'a self, allowed: &[&'a str], default: &'a str) -> &'a str {
70 match &self.sort {
71 Some(col) if allowed.contains(&col.as_str()) => col.as_str(),
72 _ => default,
73 }
74 }
75}
76
77#[derive(Debug, Serialize)]
79pub struct Paginated<T: Serialize> {
80 pub data: Vec<T>,
81 pub meta: PaginationMeta,
82}
83
84#[derive(Debug, Serialize)]
85pub struct PaginationMeta {
86 pub page: u32,
87 pub per_page: u32,
88 pub total: u64,
89 pub total_pages: u32,
90 pub has_next: bool,
91 pub has_prev: bool,
92}
93
94impl<T: Serialize> Paginated<T> {
95 pub fn new(data: Vec<T>, total: u64, params: &PaginationParams) -> Self {
97 let total_pages = ((total as f64) / (params.per_page as f64)).ceil() as u32;
98 Self {
99 data,
100 meta: PaginationMeta {
101 page: params.page,
102 per_page: params.per_page,
103 total,
104 total_pages,
105 has_next: params.page < total_pages,
106 has_prev: params.page > 1,
107 },
108 }
109 }
110}