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")]
367#[derive(Debug, Deserialize, Validate)]
368pub struct TimeParam {
369 pub created_start: Option<DateTimeUtc>,
371 pub created_end: Option<DateTimeUtc>,
373
374 pub updated_start: Option<DateTimeUtc>,
376 pub updated_end: Option<DateTimeUtc>,
378}
379
380#[cfg(test)]
381mod tests {
382 use super::*;
383
384 #[test]
385 fn page_data_new() {
386 let page = PageData::new((vec![1, 2, 3], 7));
387 assert_eq!(page.total, 7);
388 assert_eq!(page.data, vec![1, 2, 3]);
389 }
390
391 #[test]
392 fn page_data_serialize() {
393 let value = serde_json::to_value(PageData::new((vec!["a"], 1))).unwrap();
394 assert_eq!(value, serde_json::json!({"total": 1, "data": ["a"]}));
395 }
396
397 #[test]
398 fn pagination_default() {
399 let p = PaginationParam::default();
400 assert_eq!((p.page, p.size), (1, 10));
401 assert_eq!((p.left(), p.right()), (0, 10));
402
403 let p = PaginationParam::new();
404 assert_eq!((p.page, p.size), (1, 10));
405 }
406
407 #[test]
408 fn pagination_deserialize() {
409 let p: PaginationParam = serde_json::from_str(r#"{"page": 2, "size": 20}"#).unwrap();
410 assert_eq!((p.page, p.size), (2, 20));
411 let p: PaginationParam = serde_json::from_str("{}").unwrap();
413 assert_eq!((p.page, p.size), (1, 10));
414 assert!(serde_json::from_str::<PaginationParam>(r#"{"page": "2"}"#).is_err());
416 }
417
418 #[test]
419 fn pagination_validate() {
420 let p: PaginationParam = serde_json::from_str(r#"{"page": 0}"#).unwrap();
422 assert!(p.validate().is_err());
423 let p: PaginationParam = serde_json::from_str(r#"{"size": 101}"#).unwrap();
424 assert!(p.validate().is_err());
425 let p: PaginationParam = serde_json::from_str(r#"{"page": 5, "size": 100}"#).unwrap();
426 assert!(p.validate().is_ok());
427 }
428
429 #[test]
430 fn pagination_split() {
431 let p = PaginationParam { page: 2, size: 3 };
432 let page = p.split(vec![1, 2, 3, 4, 5, 6, 7]);
433 assert_eq!(page.total, 7);
434 assert_eq!(page.data, vec![4, 5, 6]);
435 }
436
437 #[test]
438 fn pagination_bounds_saturate() {
439 let p = PaginationParam { page: 0, size: 10 };
441 assert_eq!((p.left(), p.right()), (0, 0));
442
443 let p = PaginationParam {
445 page: u64::MAX,
446 size: 100,
447 };
448 assert_eq!(p.left(), u64::MAX);
449 assert_eq!(p.right(), u64::MAX);
450 assert_eq!(PaginationParam::round(u64::MAX), usize::MAX as usize);
451
452 let page = p.split(vec![1, 2, 3]);
454 assert_eq!(page.total, 3);
455 assert!(page.data.is_empty());
456 }
457
458 #[test]
459 fn unique_validator_works() {
460 assert!(unique_validator(&Vec::<u8>::new()).is_ok());
461 assert!(unique_validator(&vec![1, 2, 3]).is_ok());
462 assert!(unique_validator(&vec![1, 2, 2]).is_err());
463 }
464
465 #[test]
466 fn ids_req_validate() {
467 let a = HyUuid::new();
468 let b = HyUuid::new();
469
470 let req: IDsReq =
471 serde_json::from_value(serde_json::json!({ "id": [a.to_string(), b.to_string()] }))
472 .unwrap();
473 assert!(req.validate().is_ok());
474
475 let req: IDsReq =
477 serde_json::from_value(serde_json::json!({ "id": [a.to_string(), a.to_string()] }))
478 .unwrap();
479 assert!(req.validate().is_err());
480 }
481
482 #[test]
483 fn option_ids_req_validate() {
484 let a = HyUuid::new();
485
486 let req: OptionIDsReq = serde_json::from_value(serde_json::json!({})).unwrap();
488 assert!(req.id.is_none());
489 assert!(req.validate().is_ok());
490 let req: OptionIDsReq = serde_json::from_value(serde_json::json!({ "id": null })).unwrap();
491 assert!(req.validate().is_ok());
492
493 let req: OptionIDsReq =
494 serde_json::from_value(serde_json::json!({ "id": [a.to_string()] })).unwrap();
495 assert!(req.validate().is_ok());
496 let req: OptionIDsReq =
497 serde_json::from_value(serde_json::json!({ "id": [a.to_string(), a.to_string()] }))
498 .unwrap();
499 assert!(req.validate().is_err());
500 }
501
502 #[cfg(feature = "seaorm")]
503 #[test]
504 fn time_param_deserialize() {
505 let p: TimeParam = serde_json::from_str(
506 r#"{"created_start": "2024-01-01T00:00:00Z", "created_end": "2024-01-01T23:59:59.5+08:00"}"#,
507 )
508 .unwrap();
509 assert!(p.created_start.is_some());
510 assert!(p.created_end.is_some());
511 assert!(p.updated_start.is_none());
512 assert!(p.updated_end.is_none());
513 let expect: DateTimeUtc = "2024-01-01T15:59:59.5Z".parse().unwrap();
515 assert_eq!(p.created_end.unwrap(), expect);
516
517 assert!(
521 serde_json::from_str::<TimeParam>(r#"{"created_start": "2024-01-01T00:00:00"}"#)
522 .is_err()
523 );
524 }
525}