1use std::cmp;
5#[cfg(feature = "seaorm")]
6use std::collections::BTreeMap;
7use std::hash::Hash;
8
9use actix_cloud::utils;
10use anyhow::Result;
11use derivative::Derivative;
12#[cfg(feature = "seaorm")]
13use sea_orm::{
14 ExprTrait as _, FromQueryResult, Order, QueryOrder as _,
15 entity::prelude::async_trait::async_trait,
16 prelude::*,
17 sea_query::{ColumnRef, LikeExpr, SimpleExpr},
18};
19use serde::{Deserialize, Serialize};
20use serde_inline_default::serde_inline_default;
21use validator::{Validate, ValidationError};
22
23use crate::HyUuid;
24#[cfg(feature = "entity")]
25use crate::entity::DefaultColumnTrait;
26#[cfg(feature = "seaorm")]
27use crate::utils::StringUtil as _;
28
29#[derive(Serialize, Debug)]
31pub struct PageData<T> {
32 pub total: u64,
34 pub data: Vec<T>,
36}
37
38impl<T> PageData<T> {
39 pub fn new(data: (Vec<T>, u64)) -> Self {
42 Self {
43 total: data.1,
44 data: data.0,
45 }
46 }
47}
48
49#[serde_inline_default]
55#[derive(Derivative, Deserialize, Validate, Clone, Debug)]
56#[derivative(Default(new = "true"))]
57pub struct PaginationParam {
58 #[validate(range(min = 1))]
60 #[serde_inline_default(1)]
61 #[derivative(Default(value = "1"))]
62 pub page: u64,
63
64 #[validate(range(min = 1, max = 100))]
66 #[serde_inline_default(10)]
67 #[derivative(Default(value = "10"))]
68 pub size: u64,
69}
70
71impl PaginationParam {
72 pub fn left(&self) -> u64 {
74 self.page.saturating_sub(1).saturating_mul(self.size)
75 }
76
77 pub fn right(&self) -> u64 {
79 self.page.saturating_mul(self.size)
80 }
81
82 pub fn round(p: u64) -> usize {
85 cmp::min(p, usize::MAX as u64) as usize
86 }
87
88 pub fn split<T>(&self, data: Vec<T>) -> PageData<T> {
91 let cnt = data.len();
92 PageData::new((
93 data.into_iter()
94 .skip(Self::round(self.left()))
95 .take(Self::round(self.size))
96 .collect(),
97 cnt as u64,
98 ))
99 }
100}
101
102#[cfg(feature = "seaorm")]
105#[async_trait]
106pub trait SelectPage<E, C>
107where
108 E: EntityTrait,
109 C: ConnectionTrait,
110{
111 async fn select_page(self, db: &C, p: Option<PaginationParam>) -> Result<(Vec<E::Model>, u64)>;
114}
115
116#[cfg(feature = "seaorm")]
117#[async_trait]
118impl<M, E, C> SelectPage<E, C> for Select<E>
119where
120 E: EntityTrait<Model = M>,
121 M: FromQueryResult + Sized + Send + Sync,
122 C: ConnectionTrait,
123{
124 async fn select_page(self, db: &C, p: Option<PaginationParam>) -> Result<(Vec<E::Model>, u64)> {
125 if let Some(page) = p {
126 let q = self.paginate(db, page.size);
127 Ok((
128 q.fetch_page(page.page.saturating_sub(1)).await?,
129 q.num_items().await?,
130 ))
131 } else {
132 let res = self.all(db).await?;
133 let cnt = res.len() as u64;
134 Ok((res, cnt))
135 }
136 }
137}
138
139#[cfg(feature = "seaorm")]
141pub trait IntoExpr {
142 fn like_expr<T>(&self, col: T) -> SimpleExpr
144 where
145 T: ColumnTrait;
146}
147
148#[cfg(feature = "seaorm")]
149impl IntoExpr for &String {
150 fn like_expr<T>(&self, col: T) -> SimpleExpr
151 where
152 T: ColumnTrait,
153 {
154 sea_orm::prelude::Expr::col((col.entity_name(), col))
155 .like(LikeExpr::new(format!("%{}%", self.like_escape())).escape('\\'))
156 }
157}
158
159#[cfg(feature = "seaorm")]
165pub struct Condition {
166 pub page: Option<PaginationParam>,
168 pub cond: sea_orm::Condition,
170 pub order: Vec<(SimpleExpr, Order)>,
172}
173
174#[cfg(feature = "seaorm")]
175impl Default for Condition {
176 fn default() -> Self {
177 Self {
178 cond: sea_orm::Condition::all(),
179 page: None,
180 order: Vec::new(),
181 }
182 }
183}
184
185#[cfg(feature = "seaorm")]
186impl From<sea_orm::Condition> for Condition {
187 fn from(cond: sea_orm::Condition) -> Self {
188 Self::new(cond)
189 }
190}
191
192#[cfg(feature = "seaorm")]
193impl Condition {
194 pub fn new(cond: sea_orm::Condition) -> Self {
196 Self {
197 cond,
198 page: None,
199 order: Vec::new(),
200 }
201 }
202
203 pub fn new_all() -> Self {
205 Self::new(Self::all())
206 }
207
208 pub fn new_any() -> Self {
210 Self::new(Self::any())
211 }
212
213 pub fn add_sort(mut self, col: SimpleExpr, sort: Order) -> Self {
215 self.order.push((col, sort));
216 self
217 }
218
219 pub fn add_page(mut self, page: PaginationParam) -> Self {
221 self.page = Some(page);
222 self
223 }
224
225 pub fn parse_sort_option(mut self, str: &Option<String>, col: Vec<ColumnRef>) -> Self {
227 if let Some(str) = str {
228 self = self.parse_sort(str, col)
229 }
230 self
231 }
232
233 pub fn parse_sort(mut self, str: &str, col: Vec<ColumnRef>) -> Self {
239 let mut allow: BTreeMap<String, ColumnRef> = col
240 .into_iter()
241 .map(|x| (x.column().unwrap().to_string(), x))
242 .collect();
243 for i in str.split(',') {
244 let order = if i.starts_with('-') {
245 Order::Desc
246 } else {
247 Order::Asc
248 };
249 let name = i
250 .strip_prefix('+')
251 .or_else(|| i.strip_prefix('-'))
252 .unwrap_or(i);
253 if let Some(x) = allow.remove(name) {
254 self = self.add_sort(SimpleExpr::Column(x), order);
255 }
256 }
257 self
258 }
259
260 pub fn any() -> sea_orm::Condition {
262 sea_orm::Condition::any()
263 }
264
265 pub fn all() -> sea_orm::Condition {
267 sea_orm::Condition::all()
268 }
269
270 #[allow(clippy::should_implement_trait)]
272 pub fn add<C>(mut self, condition: C) -> Self
273 where
274 C: Into<sea_orm::Condition>,
275 {
276 self.cond = self.cond.add(condition.into());
277 self
278 }
279
280 pub fn add_option<C>(mut self, condition: Option<C>) -> Self
282 where
283 C: Into<sea_orm::Condition>,
284 {
285 self.cond = self.cond.add_option(condition);
286 self
287 }
288
289 pub fn build<E>(self, mut q: Select<E>) -> (Select<E>, Option<PaginationParam>)
294 where
295 E: EntityTrait,
296 {
297 for i in self.order {
298 q = q.order_by(i.0, i.1);
299 }
300 (q.filter(self.cond), self.page)
301 }
302
303 pub async fn select_page<M, E, C>(self, q: Select<E>, db: &C) -> Result<(Vec<E::Model>, u64)>
306 where
307 E: EntityTrait<Model = M>,
308 M: FromQueryResult + Sized + Send + Sync,
309 C: ConnectionTrait,
310 {
311 let (q, page) = self.build(q);
312 q.select_page(db, page).await
313 }
314
315 #[cfg(feature = "entity")]
319 pub fn add_time<T>(mut self, p: &TimeParam) -> Self
320 where
321 T: DefaultColumnTrait,
322 {
323 self = self.add_option(p.created_start.map(|x| T::get_created_at().gte(x)));
324 self = self.add_option(p.created_end.map(|x| T::get_created_at().lte(x)));
325 self = self.add_option(p.updated_start.map(|x| T::get_updated_at().gte(x)));
326 self = self.add_option(p.updated_end.map(|x| T::get_updated_at().lte(x)));
327 self
328 }
329}
330
331pub fn unique_validator<T: Eq + Hash>(x: &Vec<T>) -> Result<(), ValidationError> {
336 if utils::is_unique(x) {
337 Ok(())
338 } else {
339 Err(ValidationError::new("not unique"))
340 }
341}
342
343#[derive(Debug, Validate, Deserialize)]
345pub struct IDsReq {
346 #[validate(custom(function = "unique_validator"))]
348 pub id: Vec<HyUuid>,
349}
350
351#[derive(Debug, Validate, Deserialize)]
353pub struct OptionIDsReq {
354 #[validate(custom(function = "unique_validator"))]
356 pub id: Option<Vec<HyUuid>>,
357}
358
359#[cfg(feature = "seaorm")]
366#[derive(Debug, Deserialize, Validate)]
367pub struct TimeParam {
368 pub created_start: Option<DateTime>,
370 pub created_end: Option<DateTime>,
372
373 pub updated_start: Option<DateTime>,
375 pub updated_end: Option<DateTime>,
377}
378
379#[cfg(test)]
380mod tests {
381 use super::*;
382
383 #[test]
384 fn page_data_new() {
385 let page = PageData::new((vec![1, 2, 3], 7));
386 assert_eq!(page.total, 7);
387 assert_eq!(page.data, vec![1, 2, 3]);
388 }
389
390 #[test]
391 fn page_data_serialize() {
392 let value = serde_json::to_value(PageData::new((vec!["a"], 1))).unwrap();
393 assert_eq!(value, serde_json::json!({"total": 1, "data": ["a"]}));
394 }
395
396 #[test]
397 fn pagination_default() {
398 let p = PaginationParam::default();
399 assert_eq!((p.page, p.size), (1, 10));
400 assert_eq!((p.left(), p.right()), (0, 10));
401
402 let p = PaginationParam::new();
403 assert_eq!((p.page, p.size), (1, 10));
404 }
405
406 #[test]
407 fn pagination_deserialize() {
408 let p: PaginationParam = serde_json::from_str(r#"{"page": 2, "size": 20}"#).unwrap();
409 assert_eq!((p.page, p.size), (2, 20));
410 let p: PaginationParam = serde_json::from_str("{}").unwrap();
412 assert_eq!((p.page, p.size), (1, 10));
413 assert!(serde_json::from_str::<PaginationParam>(r#"{"page": "2"}"#).is_err());
415 }
416
417 #[test]
418 fn pagination_validate() {
419 let p: PaginationParam = serde_json::from_str(r#"{"page": 0}"#).unwrap();
421 assert!(p.validate().is_err());
422 let p: PaginationParam = serde_json::from_str(r#"{"size": 101}"#).unwrap();
423 assert!(p.validate().is_err());
424 let p: PaginationParam = serde_json::from_str(r#"{"page": 5, "size": 100}"#).unwrap();
425 assert!(p.validate().is_ok());
426 }
427
428 #[test]
429 fn pagination_split() {
430 let p = PaginationParam { page: 2, size: 3 };
431 let page = p.split(vec![1, 2, 3, 4, 5, 6, 7]);
432 assert_eq!(page.total, 7);
433 assert_eq!(page.data, vec![4, 5, 6]);
434 }
435
436 #[test]
437 fn pagination_bounds_saturate() {
438 let p = PaginationParam { page: 0, size: 10 };
440 assert_eq!((p.left(), p.right()), (0, 0));
441
442 let p = PaginationParam {
444 page: u64::MAX,
445 size: 100,
446 };
447 assert_eq!(p.left(), u64::MAX);
448 assert_eq!(p.right(), u64::MAX);
449 assert_eq!(PaginationParam::round(u64::MAX), usize::MAX as usize);
450
451 let page = p.split(vec![1, 2, 3]);
453 assert_eq!(page.total, 3);
454 assert!(page.data.is_empty());
455 }
456
457 #[test]
458 fn unique_validator_works() {
459 assert!(unique_validator(&Vec::<u8>::new()).is_ok());
460 assert!(unique_validator(&vec![1, 2, 3]).is_ok());
461 assert!(unique_validator(&vec![1, 2, 2]).is_err());
462 }
463
464 #[test]
465 fn ids_req_validate() {
466 let a = HyUuid::new();
467 let b = HyUuid::new();
468
469 let req: IDsReq =
470 serde_json::from_value(serde_json::json!({ "id": [a.to_string(), b.to_string()] }))
471 .unwrap();
472 assert!(req.validate().is_ok());
473
474 let req: IDsReq =
476 serde_json::from_value(serde_json::json!({ "id": [a.to_string(), a.to_string()] }))
477 .unwrap();
478 assert!(req.validate().is_err());
479 }
480
481 #[test]
482 fn option_ids_req_validate() {
483 let a = HyUuid::new();
484
485 let req: OptionIDsReq = serde_json::from_value(serde_json::json!({})).unwrap();
487 assert!(req.id.is_none());
488 assert!(req.validate().is_ok());
489 let req: OptionIDsReq = serde_json::from_value(serde_json::json!({ "id": null })).unwrap();
490 assert!(req.validate().is_ok());
491
492 let req: OptionIDsReq =
493 serde_json::from_value(serde_json::json!({ "id": [a.to_string()] })).unwrap();
494 assert!(req.validate().is_ok());
495 let req: OptionIDsReq =
496 serde_json::from_value(serde_json::json!({ "id": [a.to_string(), a.to_string()] }))
497 .unwrap();
498 assert!(req.validate().is_err());
499 }
500
501 #[cfg(feature = "seaorm")]
502 #[test]
503 fn time_param_deserialize() {
504 let p: TimeParam = serde_json::from_str(
505 r#"{"created_start": "2024-01-01T00:00:00", "created_end": "2024-01-01T23:59:59.5"}"#,
506 )
507 .unwrap();
508 assert!(p.created_start.is_some());
509 assert!(p.created_end.is_some());
510 assert!(p.updated_start.is_none());
511 assert!(p.updated_end.is_none());
512
513 assert!(
515 serde_json::from_str::<TimeParam>(r#"{"created_start": "2024-01-01 00:00:00"}"#)
516 .is_err()
517 );
518 }
519}