opentalk_types_common/pagination/
page.rs1use snafu::{Snafu, ensure};
6
7use crate::utils::ExampleData;
8
9const DEFAULT_VALUE: i64 = 1;
10const FIRST_VALUE: i64 = 1;
11const ONE_VALUE: i64 = 1;
12const MIN_VALUE: i64 = 1;
13const MAX_VALUE: i64 = i64::MAX;
14
15#[derive(Debug, Snafu)]
17pub enum TryFromPageError {
18 #[snafu(display("Page number is outside the allowed range ({min}..={max})"))]
20 OutOfRange {
21 min: i64,
23 max: i64,
25 },
26}
27
28#[derive(
32 Debug,
33 Clone,
34 Copy,
35 PartialEq,
36 Eq,
37 PartialOrd,
38 Ord,
39 Hash,
40 derive_more::Display,
41 derive_more::AsRef,
42 derive_more::Into,
43)]
44#[cfg_attr(
45 feature = "diesel",
46 derive(
47 opentalk_diesel_newtype::DieselNewtype,
48 diesel::expression::AsExpression,
49 diesel::deserialize::FromSqlRow
50 )
51)]
52#[cfg_attr(feature="diesel", diesel(sql_type = diesel::sql_types::BigInt))]
53#[cfg_attr(
54 feature = "serde",
55 derive(serde::Serialize, serde::Deserialize),
56 serde(try_from = "i64")
57)]
58#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema), schema(example = json!(Page::example_data())))]
59pub struct Page(i64);
60
61impl Page {
62 pub const MIN: Self = Self(MIN_VALUE);
64
65 pub const MAX: Self = Self(MAX_VALUE);
67
68 pub const DEFAULT: Self = Self(DEFAULT_VALUE);
70
71 pub const FIRST: Self = Self(FIRST_VALUE);
73
74 pub const ONE: Self = Self(ONE_VALUE);
76
77 pub const fn from_i64_clamped(value: i64) -> Self {
79 let value = if value < MIN_VALUE { MIN_VALUE } else { value };
80 Self(value)
81 }
82
83 pub const fn saturating_add(self, rhs: Self) -> Self {
85 Self::from_i64_clamped(self.0.saturating_add(rhs.0))
86 }
87
88 pub const fn saturating_sub(self, rhs: Self) -> Self {
90 Self::from_i64_clamped(self.0.saturating_sub(rhs.0))
91 }
92
93 pub fn saturating_next(&self) -> Self {
95 self.saturating_add(Self::ONE)
96 }
97
98 pub fn saturating_previous(&self) -> Self {
100 self.saturating_sub(Self::ONE)
101 }
102
103 pub fn as_zero_based_i64(&self) -> i64 {
105 self.0.saturating_sub(1).clamp(0, i64::MAX)
106 }
107
108 pub fn as_zero_based_usize(&self) -> usize {
110 self.0.saturating_sub(1) as usize
111 }
112}
113
114impl Default for Page {
115 fn default() -> Self {
116 Self::DEFAULT
117 }
118}
119
120impl TryFrom<u64> for Page {
121 type Error = TryFromPageError;
122
123 fn try_from(value: u64) -> Result<Self, Self::Error> {
124 let value = i64::try_from(value).map_err(|_e| TryFromPageError::OutOfRange {
125 min: MIN_VALUE,
126 max: MAX_VALUE,
127 })?;
128 Ok(Self(value))
129 }
130}
131
132impl TryFrom<i32> for Page {
133 type Error = TryFromPageError;
134
135 fn try_from(value: i32) -> Result<Self, Self::Error> {
136 Self::try_from(i64::from(value))
137 }
138}
139
140impl TryFrom<usize> for Page {
141 type Error = TryFromPageError;
142
143 fn try_from(value: usize) -> Result<Self, Self::Error> {
144 let value = i64::try_from(value).map_err(|_e| TryFromPageError::OutOfRange {
145 min: MIN_VALUE,
146 max: MAX_VALUE,
147 })?;
148 Ok(Self(value))
149 }
150}
151
152impl TryFrom<i64> for Page {
153 type Error = TryFromPageError;
154
155 fn try_from(value: i64) -> Result<Self, Self::Error> {
156 ensure!(
157 (MIN_VALUE..=MAX_VALUE).contains(&value),
158 OutOfRangeSnafu {
159 min: MIN_VALUE,
160 max: MAX_VALUE
161 }
162 );
163 Ok(Self(value))
164 }
165}
166
167impl From<Page> for usize {
168 fn from(Page(value): Page) -> Self {
169 usize::try_from(value).unwrap()
170 }
171}
172
173impl ExampleData for Page {
174 fn example_data() -> Self {
175 Self(5)
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use pretty_assertions::{assert_eq, assert_matches};
182
183 use super::{MAX_VALUE, MIN_VALUE, Page, TryFromPageError};
184
185 #[test]
186 fn saturating_add() {
187 assert_eq!(
188 Page::try_from(5423)
189 .unwrap()
190 .saturating_add(Page::try_from(3).unwrap()),
191 Page::try_from(5426).unwrap()
192 );
193 assert_eq!(
194 Page::MAX.saturating_add(Page::try_from(3).unwrap()),
195 Page::MAX
196 );
197 }
198
199 #[test]
200 fn try_from() {
201 assert_eq!(Page::try_from(1usize).unwrap(), Page::FIRST);
202 assert_eq!(Page::try_from(1i32).unwrap(), Page::FIRST);
203 assert_eq!(Page::try_from(1i64).unwrap(), Page::FIRST);
204 assert_eq!(Page::try_from(1u64).unwrap(), Page::FIRST);
205
206 assert_eq!(Page::try_from(14usize).unwrap(), Page(14));
207 assert_eq!(Page::try_from(15i32).unwrap(), Page(15));
208 assert_eq!(Page::try_from(16i64).unwrap(), Page(16));
209 assert_eq!(Page::try_from(18u64).unwrap(), Page(18));
210
211 assert_matches!(
212 Page::try_from(-42i64),
213 Err(TryFromPageError::OutOfRange {
214 min: MIN_VALUE,
215 max: MAX_VALUE
216 })
217 );
218 assert_matches!(
219 Page::try_from((i64::MAX as usize) + 1),
220 Err(TryFromPageError::OutOfRange {
221 min: MIN_VALUE,
222 max: MAX_VALUE
223 })
224 );
225 assert_matches!(
226 Page::try_from((i64::MAX as u64) + 1),
227 Err(TryFromPageError::OutOfRange {
228 min: MIN_VALUE,
229 max: MAX_VALUE
230 })
231 );
232 }
233}
234
235#[cfg(all(test, feature = "serde"))]
236mod serde_tests {
237 use pretty_assertions::assert_eq;
238 use serde_json::json;
239
240 use super::Page;
241
242 #[test]
243 fn serialize_default() {
244 let example = Page::default();
245 assert_eq!(json!(example), json!(1));
246 }
247
248 #[test]
249 fn serialize() {
250 let example = Page::try_from(423).unwrap();
251 assert_eq!(json!(example), json!(423));
252 }
253
254 #[test]
255 fn deserialize_invalid_zero() {
256 assert!(serde_json::from_value::<Page>(json!(0)).is_err());
257 }
258
259 #[test]
260 fn deserialize_default() {
261 let example = Page::default();
262 assert_eq!(example, serde_json::from_value(json!(1)).unwrap());
263 }
264
265 #[test]
266 fn deserialize() {
267 let example = Page::try_from(64).unwrap();
268 assert_eq!(example, serde_json::from_value(json!(64)).unwrap());
269 }
270}