1use crate::tiny_safe_string::TinySafeString;
2use serde::{Deserialize, Serialize, Serializer, Deserializer};
3use std::fmt;
4use serde::de::{self, Visitor};
5
6#[cfg_attr(feature = "utoipa-schema", derive(utoipa::ToSchema))]
7#[derive(Debug, Clone)]
8pub struct PaginationData {
9 pub page: Option<i64>, pub page_size: Option<i64>, pub sort_by: Option<TinySafeString>, pub sort_dir: Option<ColumnSortDir>, }
14
15#[cfg_attr(feature = "utoipa-schema", derive(utoipa::ToSchema))]
16#[derive(Debug, Clone)]
17pub enum ColumnSortDir {
18 Asc,
19 Desc
20}
21
22impl Serialize for ColumnSortDir {
24 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
25 where
26 S: Serializer,
27 {
28 match self {
29 ColumnSortDir::Asc => serializer.serialize_str("asc"),
30 ColumnSortDir::Desc => serializer.serialize_str("desc"),
31 }
32 }
33}
34
35impl<'de> Deserialize<'de> for ColumnSortDir {
37 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
38 where
39 D: Deserializer<'de>,
40 {
41 struct ColumnSortDirVisitor;
42 impl<'de> Visitor<'de> for ColumnSortDirVisitor {
43 type Value = ColumnSortDir;
44 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
45 formatter.write_str("a string representing sort direction: 'asc' or 'desc'")
46 }
47 fn visit_str<E>(self, value: &str) -> Result<ColumnSortDir, E>
48 where
49 E: de::Error,
50 {
51 match value.to_lowercase().as_str() {
52 "asc" => Ok(ColumnSortDir::Asc),
53 "desc" => Ok(ColumnSortDir::Desc),
54 _ => Ok(ColumnSortDir::Desc), }
56 }
57 }
58 deserializer.deserialize_str(ColumnSortDirVisitor)
59 }
60}
61
62impl Serialize for PaginationData {
64 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
65 where
66 S: Serializer,
67 {
68 use serde::ser::SerializeStruct;
69 let mut state = serializer.serialize_struct("PaginationData", 4)?;
70 state.serialize_field("page", &self.page)?;
71 state.serialize_field("page_size", &self.page_size)?;
72 state.serialize_field("sort_by", &self.sort_by)?;
73 state.serialize_field("sort_dir", &self.sort_dir)?;
74 state.end()
75 }
76}
77
78impl<'de> Deserialize<'de> for PaginationData {
80 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
81 where
82 D: Deserializer<'de>,
83 {
84 #[derive(Deserialize)]
85 struct PaginationDataHelper {
86 page: Option<i64>,
87 page_size: Option<i64>,
88 sort_by: Option<TinySafeString>,
89 sort_dir: Option<ColumnSortDir>,
90 }
91 let helper = PaginationDataHelper::deserialize(deserializer)?;
92
93 Ok(PaginationData {
94 page: helper.page,
95 page_size: helper.page_size,
96 sort_by: helper.sort_by,
97 sort_dir: helper.sort_dir,
98 })
99 }
100}
101
102impl Default for PaginationData {
103 fn default() -> Self {
104 Self {
105 page: Some(1),
106 page_size: Some(10),
107 sort_by: Some("created_at".try_into().unwrap()),
108 sort_dir: Some(ColumnSortDir::Desc),
109 }
110 }
111}
112
113impl ColumnSortDir {
114 pub fn to_sql_string(&self) -> &'static str {
116 match self {
117 ColumnSortDir::Asc => "ASC",
118 ColumnSortDir::Desc => "DESC",
119 }
120 }
121}
122
123impl PaginationData {
124 pub fn get_limit(&self) -> i64 {
126 self.page_size.unwrap_or(10).min(100) }
128
129 pub fn get_offset(&self) -> i64 {
131 let page = self.page.unwrap_or(1).max(1) - 1; page * self.get_limit()
133 }
134
135 pub fn get_order_by(&self) -> String {
137 let column = match &self.sort_by {
138 Some(col) => col.to_sql_string(),
139 None => "created_at", };
141
142 let direction = match &self.sort_dir {
143 Some(dir) => dir.to_sql_string(),
144 None => "DESC", };
146
147 format!("{} {}", column, direction)
148 }
149
150 pub fn build_query_part(&self) -> String {
152 format!(
153 "ORDER BY {} LIMIT {} OFFSET {}",
154 self.get_order_by(),
155 self.get_limit(),
156 self.get_offset()
157 )
158 }
159}
160
161#[cfg_attr(feature = "utoipa-schema", derive(utoipa::ToSchema))]
162#[derive(Serialize)]
163pub struct PaginatedResponse<T> {
164 pub items: Vec<T>,
165 pub total_count: i64,
166 pub page: i64,
167 pub page_size: i64,
168 pub total_pages: i64,
169}