1use orbital_data::DataValue;
31use serde::{Deserialize, Serialize};
32
33#[cfg(feature = "leptos")]
34mod infinite_scroll;
35#[cfg(feature = "leptos")]
36pub use infinite_scroll::*;
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
40#[serde(rename_all = "lowercase")]
41pub enum SortDirectionWire {
42 Asc,
43 Desc,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
48pub struct SortParam {
49 pub field: String,
50 pub direction: SortDirectionWire,
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
55#[serde(rename_all = "lowercase")]
56pub enum FilterLogicWire {
57 #[default]
58 And,
59 Or,
60}
61
62#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
64pub struct FilterRuleParam {
65 pub field: String,
66 pub operator: String,
67 pub value: DataValue,
68}
69
70#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
72pub struct FilterQuery {
73 pub items: Vec<FilterRuleParam>,
74 pub logic: FilterLogicWire,
75}
76
77#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
79pub struct PageRequest {
80 pub offset: u32,
82 pub limit: u32,
84 #[serde(default, skip_serializing_if = "Option::is_none")]
86 pub sort: Option<Vec<SortParam>>,
87 #[serde(default, skip_serializing_if = "Option::is_none")]
89 pub filter: Option<FilterQuery>,
90 #[serde(default, skip_serializing_if = "Option::is_none")]
92 pub quick_search: Option<String>,
93}
94
95impl PageRequest {
96 pub fn new(offset: u32, limit: u32) -> Self {
98 Self {
99 offset,
100 limit,
101 sort: None,
102 filter: None,
103 quick_search: None,
104 }
105 }
106
107 pub fn with_query(
109 offset: u32,
110 limit: u32,
111 sort: Option<Vec<SortParam>>,
112 filter: Option<FilterQuery>,
113 quick_search: Option<String>,
114 ) -> Self {
115 Self {
116 offset,
117 limit,
118 sort,
119 filter,
120 quick_search,
121 }
122 }
123
124 pub fn is_first_page(&self) -> bool {
126 self.offset == 0
127 }
128}
129
130#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
140pub struct Page<T> {
141 pub items: Vec<T>,
143 pub has_more: bool,
145 pub total_count: Option<u64>,
148 #[serde(default)]
156 pub next_request_offset: Option<u32>,
157}
158
159impl<T> Page<T> {
160 pub fn from_oversized(mut raw_items: Vec<T>, limit: u32, total_count: Option<u64>) -> Self {
165 let has_more = raw_items.len() as u32 > limit;
166 if has_more {
167 raw_items.truncate(limit as usize);
168 }
169 Self {
170 items: raw_items,
171 has_more,
172 total_count,
173 next_request_offset: None,
174 }
175 }
176
177 pub fn empty() -> Self {
179 Self {
180 items: Vec::new(),
181 has_more: false,
182 total_count: Some(0),
183 next_request_offset: None,
184 }
185 }
186
187 pub fn len(&self) -> usize {
189 self.items.len()
190 }
191
192 pub fn is_empty(&self) -> bool {
194 self.items.is_empty()
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201
202 #[test]
203 fn from_oversized_trims_and_sets_has_more() {
204 let items: Vec<u32> = (1..=11).collect(); let page = Page::from_oversized(items, 10, Some(25));
206 assert_eq!(page.items.len(), 10);
207 assert!(page.has_more);
208 assert_eq!(page.total_count, Some(25));
209 }
210
211 #[test]
212 fn from_oversized_no_extra_item() {
213 let items: Vec<u32> = (1..=10).collect(); let page = Page::from_oversized(items, 10, None);
215 assert_eq!(page.items.len(), 10);
216 assert!(!page.has_more);
217 assert_eq!(page.total_count, None);
218 }
219
220 #[test]
221 fn page_request_first_page() {
222 assert!(PageRequest::new(0, 10).is_first_page());
223 assert!(!PageRequest::new(10, 10).is_first_page());
224 }
225
226 #[test]
227 fn page_request_legacy_deserialize() {
228 let json = r#"{"offset":0,"limit":10}"#;
229 let req: PageRequest = serde_json::from_str(json).unwrap();
230 assert_eq!(req.offset, 0);
231 assert_eq!(req.limit, 10);
232 assert!(req.sort.is_none());
233 assert!(req.filter.is_none());
234 assert!(req.quick_search.is_none());
235 }
236
237 #[test]
238 fn empty_page() {
239 let page: Page<String> = Page::empty();
240 assert!(page.is_empty());
241 assert!(!page.has_more);
242 assert_eq!(page.total_count, Some(0));
243 }
244}