mcsr_ranked_api/pagination/
mod.rs

1use std::{error::Error, fmt::Display, num::NonZeroU8};
2
3use serde::Serialize;
4
5use crate::types::MatchId;
6
7const MAX_COUNT: u8 = 100;
8
9#[cfg(test)]
10mod tests;
11
12#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
13pub struct Pagination {
14	pub count: NonZeroU8,
15	#[serde(flatten)]
16	pub position: RelativePos,
17}
18
19impl Default for Pagination {
20	fn default() -> Self {
21		Self {
22			count: const { NonZeroU8::new(20).unwrap() },
23			position: Default::default(),
24		}
25	}
26}
27
28impl Pagination {
29	/// Attempt to construct a pagination given a `count` and `position`
30	pub fn new(count: u8, position: RelativePos) -> Option<Self> {
31		NonZeroU8::new(count)
32			.filter(|_| count <= MAX_COUNT)
33			.map(|count| Self { count, position })
34	}
35
36	/// Create a new pagination given a `count` and `position`
37	/// without checking for `count` bounds
38	///
39	/// # Safety
40	/// this function calls `NonZeroU8::new_unchecked`.
41	///
42	/// Passing `count = 0` will result in undefined behavior.
43	///
44	/// Passing `count` > [`MAX_COUNT`] will result in an API error
45	pub unsafe fn new_unchecked(count: u8, position: RelativePos) -> Self {
46		Self {
47			count: unsafe { NonZeroU8::new_unchecked(count) },
48			position,
49		}
50	}
51
52	/// Attempt to construct a pagination given a `count` without relative position
53	pub fn count(count: u8) -> Option<Self> {
54		NonZeroU8::new(count).map(|count| Self {
55			count,
56			..Default::default()
57		})
58	}
59
60	/// Create a new pagination given a `count` without a relative position
61	/// without checking for `count` bounds
62	///
63	/// # Safety
64	/// This function calls `NonZeroU8::new_unchecked`.
65	///
66	/// Passing `count = 0` will result in undefined behavior.
67	///
68	/// Passing `count` > [`MAX_COUNT`] will result in an API error
69	pub unsafe fn count_unchecked(count: u8) -> Self {
70		Self {
71			count: unsafe { NonZeroU8::new_unchecked(count) },
72			..Default::default()
73		}
74	}
75}
76
77impl From<RelativePos> for Pagination {
78	fn from(position: RelativePos) -> Self {
79		Self {
80			position,
81			..Default::default()
82		}
83	}
84}
85
86#[derive(Debug, Clone)]
87pub struct RelativePosError {
88	/// The `before` match id
89	pub before: MatchId,
90	/// The `after` match id
91	pub after: MatchId,
92}
93impl Display for RelativePosError {
94	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95		write!(
96			f,
97			"Cannot create RelativePos, before = {} > after = {}",
98			self.before, self.after
99		)
100	}
101}
102impl Error for RelativePosError {}
103
104#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize)]
105#[serde(rename_all = "lowercase")]
106pub struct RelativePos {
107	#[serde(skip_serializing_if = "Option::is_none")]
108	before: Option<MatchId>,
109	#[serde(skip_serializing_if = "Option::is_none")]
110	after: Option<MatchId>,
111}
112impl RelativePos {
113	/// Creates a new [`RelativePos`] after checking whether [`before`, `after`] is a valid range.
114	pub fn new_checked(before: MatchId, after: MatchId) -> Result<Self, RelativePosError> {
115		if after > before {
116			Err(RelativePosError { before, after })
117		} else {
118			Ok(Self {
119				before: Some(before),
120				after: Some(after),
121			})
122		}
123	}
124	/// Creates a new [`RelativePos`] without checking `before` and `after`.
125	///
126	/// If you are taking input from a user, it is recommended to use [`RelativePos::new_checked`] instead.
127	pub fn new(before: MatchId, after: MatchId) -> Self {
128		Self {
129			before: Some(before),
130			after: Some(after),
131		}
132	}
133	/// Creates a [`RelativePos`] with only the `before` field set
134	pub fn before(before: MatchId) -> Self {
135		Self {
136			before: Some(before),
137			after: None,
138		}
139	}
140	/// Creates a [`RelativePos`] with only the `after` field set
141	pub fn after(after: MatchId) -> Self {
142		Self {
143			before: None,
144			after: Some(after),
145		}
146	}
147}