Skip to main content

actix_cloud_extra/
api.rs

1use std::cmp;
2#[cfg(feature = "seaorm")]
3use std::collections::BTreeMap;
4use std::hash::Hash;
5
6use actix_cloud::utils;
7use anyhow::Result;
8use derivative::Derivative;
9#[cfg(feature = "seaorm")]
10use sea_orm::{
11    ExprTrait as _, FromQueryResult, Order, QueryOrder as _,
12    entity::prelude::async_trait::async_trait,
13    prelude::*,
14    sea_query::{ColumnRef, LikeExpr, SimpleExpr},
15};
16use serde::{Deserialize, Serialize};
17use serde_inline_default::serde_inline_default;
18use serde_with::{DisplayFromStr, serde_as};
19use validator::{Validate, ValidationError};
20
21use crate::HyUuid;
22#[cfg(feature = "entity")]
23use crate::entity::DefaultColumnTrait;
24#[cfg(feature = "seaorm")]
25use crate::utils::StringUtil as _;
26
27#[derive(Serialize, Debug)]
28pub struct PageData<T> {
29    pub total: u64,
30    pub data: Vec<T>,
31}
32
33impl<T> PageData<T> {
34    pub fn new(data: (Vec<T>, u64)) -> Self {
35        Self {
36            total: data.1,
37            data: data.0,
38        }
39    }
40}
41
42#[serde_as]
43#[serde_inline_default]
44#[derive(Derivative, Deserialize, Validate, Clone, Debug)]
45#[derivative(Default(new = "true"))]
46pub struct PaginationParam {
47    #[validate(range(min = 1))]
48    #[serde_inline_default(1)]
49    #[derivative(Default(value = "1"))]
50    #[serde_as(as = "DisplayFromStr")]
51    pub page: u64,
52
53    #[validate(range(min = 1, max = 100))]
54    #[serde_inline_default(10)]
55    #[derivative(Default(value = "10"))]
56    #[serde_as(as = "DisplayFromStr")]
57    pub size: u64,
58}
59
60impl PaginationParam {
61    pub fn left(&self) -> u64 {
62        self.page.saturating_sub(1).saturating_mul(self.size)
63    }
64
65    pub fn right(&self) -> u64 {
66        self.page.saturating_mul(self.size)
67    }
68
69    pub fn round(p: u64) -> usize {
70        cmp::min(p, usize::MAX as u64) as usize
71    }
72
73    pub fn split<T>(&self, data: Vec<T>) -> PageData<T> {
74        let cnt = data.len();
75        PageData::new((
76            data.into_iter()
77                .skip(Self::round(self.left()))
78                .take(Self::round(self.size))
79                .collect(),
80            cnt as u64,
81        ))
82    }
83}
84
85#[cfg(feature = "seaorm")]
86#[async_trait]
87pub trait SelectPage<E, C>
88where
89    E: EntityTrait,
90    C: ConnectionTrait,
91{
92    async fn select_page(self, db: &C, p: Option<PaginationParam>) -> Result<(Vec<E::Model>, u64)>;
93}
94
95#[cfg(feature = "seaorm")]
96#[async_trait]
97impl<M, E, C> SelectPage<E, C> for Select<E>
98where
99    E: EntityTrait<Model = M>,
100    M: FromQueryResult + Sized + Send + Sync,
101    C: ConnectionTrait,
102{
103    async fn select_page(self, db: &C, p: Option<PaginationParam>) -> Result<(Vec<E::Model>, u64)> {
104        if let Some(page) = p {
105            let q = self.paginate(db, page.size);
106            Ok((q.fetch_page(page.page - 1).await?, q.num_items().await?))
107        } else {
108            let res = self.all(db).await?;
109            let cnt = res.len() as u64;
110            Ok((res, cnt))
111        }
112    }
113}
114
115#[cfg(feature = "seaorm")]
116pub trait IntoExpr {
117    fn like_expr<T>(&self, col: T) -> SimpleExpr
118    where
119        T: ColumnTrait;
120}
121
122#[cfg(feature = "seaorm")]
123impl IntoExpr for &String {
124    fn like_expr<T>(&self, col: T) -> SimpleExpr
125    where
126        T: ColumnTrait,
127    {
128        sea_orm::prelude::Expr::col((col.entity_name(), col))
129            .like(LikeExpr::new(format!("%{}%", self.like_escape())).escape('\\'))
130    }
131}
132
133#[cfg(feature = "seaorm")]
134pub struct Condition {
135    pub page: Option<PaginationParam>,
136    pub cond: sea_orm::Condition,
137    pub order: Vec<(SimpleExpr, Order)>,
138}
139
140#[cfg(feature = "seaorm")]
141impl Default for Condition {
142    fn default() -> Self {
143        Self {
144            cond: sea_orm::Condition::all(),
145            page: None,
146            order: Vec::new(),
147        }
148    }
149}
150
151#[cfg(feature = "seaorm")]
152impl From<sea_orm::Condition> for Condition {
153    fn from(cond: sea_orm::Condition) -> Self {
154        Self::new(cond)
155    }
156}
157
158#[cfg(feature = "seaorm")]
159impl Condition {
160    pub fn new(cond: sea_orm::Condition) -> Self {
161        Self {
162            cond,
163            page: None,
164            order: Vec::new(),
165        }
166    }
167
168    pub fn new_all() -> Self {
169        Self::new(Self::all())
170    }
171
172    pub fn new_any() -> Self {
173        Self::new(Self::any())
174    }
175
176    pub fn add_sort(mut self, col: SimpleExpr, sort: Order) -> Self {
177        self.order.push((col, sort));
178        self
179    }
180
181    pub fn add_page(mut self, page: PaginationParam) -> Self {
182        self.page = Some(page);
183        self
184    }
185
186    pub fn parse_sort(mut self, str: String, col: Vec<ColumnRef>) -> Self {
187        let mut allow: BTreeMap<String, ColumnRef> = col
188            .into_iter()
189            .map(|x| (x.column().unwrap().to_string(), x))
190            .collect();
191        for i in str.split(',') {
192            let order = if i.starts_with("-") {
193                Order::Desc
194            } else {
195                Order::Asc
196            };
197            let name = i
198                .strip_prefix('+')
199                .or_else(|| i.strip_prefix('-'))
200                .unwrap_or(i);
201            if let Some(x) = allow.remove(name) {
202                self = self.add_sort(SimpleExpr::Column(x), order);
203            }
204        }
205        self
206    }
207
208    pub fn any() -> sea_orm::Condition {
209        sea_orm::Condition::any()
210    }
211
212    pub fn all() -> sea_orm::Condition {
213        sea_orm::Condition::all()
214    }
215
216    #[allow(clippy::should_implement_trait)]
217    pub fn add<C>(mut self, condition: C) -> Self
218    where
219        C: Into<sea_orm::Condition>,
220    {
221        self.cond = self.cond.add(condition.into());
222        self
223    }
224
225    pub fn add_option<C>(mut self, condition: Option<C>) -> Self
226    where
227        C: Into<sea_orm::Condition>,
228    {
229        self.cond = self.cond.add_option(condition);
230        self
231    }
232
233    pub fn build<E>(self, mut q: Select<E>) -> (Select<E>, Option<PaginationParam>)
234    where
235        E: EntityTrait,
236    {
237        for i in self.order {
238            q = q.order_by(i.0, i.1);
239        }
240        (q.filter(self.cond), self.page)
241    }
242
243    pub async fn select_page<M, E, C>(self, q: Select<E>, db: &C) -> Result<(Vec<E::Model>, u64)>
244    where
245        E: EntityTrait<Model = M>,
246        M: FromQueryResult + Sized + Send + Sync,
247        C: ConnectionTrait,
248    {
249        let (q, page) = self.build(q);
250        q.select_page(db, page).await
251    }
252
253    #[cfg(feature = "entity")]
254    pub fn add_time<T>(mut self, p: &TimeParam) -> Self
255    where
256        T: DefaultColumnTrait,
257    {
258        self = self.add_option(p.created_start.map(|x| T::get_created_at().gte(x)));
259        self = self.add_option(p.created_end.map(|x| T::get_created_at().lte(x)));
260        self = self.add_option(p.updated_start.map(|x| T::get_updated_at().gte(x)));
261        self = self.add_option(p.updated_end.map(|x| T::get_updated_at().lte(x)));
262        self
263    }
264}
265
266/// # Errors
267/// Will return `Err` when `x` has duplicate items.
268pub fn unique_validator<T: Eq + Hash>(x: &Vec<T>) -> Result<(), ValidationError> {
269    if utils::is_unique(x) {
270        Ok(())
271    } else {
272        Err(ValidationError::new("not unique"))
273    }
274}
275
276#[derive(Debug, Validate, Deserialize)]
277pub struct IDsReq {
278    #[validate(custom(function = "unique_validator"))]
279    pub id: Vec<HyUuid>,
280}
281
282#[derive(Debug, Validate, Deserialize)]
283pub struct OptionIDsReq {
284    #[validate(custom(function = "unique_validator"))]
285    pub id: Option<Vec<HyUuid>>,
286}
287
288#[serde_as]
289#[derive(Debug, Deserialize, Validate)]
290pub struct TimeParam {
291    #[validate(range(min = 0))]
292    #[serde_as(as = "Option<DisplayFromStr>")]
293    pub created_start: Option<i64>,
294    #[validate(range(min = 0))]
295    #[serde_as(as = "Option<DisplayFromStr>")]
296    pub created_end: Option<i64>,
297
298    #[validate(range(min = 0))]
299    #[serde_as(as = "Option<DisplayFromStr>")]
300    pub updated_start: Option<i64>,
301    #[validate(range(min = 0))]
302    #[serde_as(as = "Option<DisplayFromStr>")]
303    pub updated_end: Option<i64>,
304}