Skip to main content

actix_cloud_extra_macros/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3use proc_macro::TokenStream;
4use quote::quote;
5use syn::{Ident, ImplItem, ItemImpl, parse_macro_input, parse_quote};
6
7/// Implement default viewer.
8///
9/// # Examples
10/// ```ignore
11/// pub struct UserViewer;
12///
13/// #[default_viewer]
14/// impl UserViewer {}
15/// ```
16#[proc_macro_attribute]
17pub fn default_viewer(attr: TokenStream, input: TokenStream) -> TokenStream {
18    let attr = parse_macro_input!(attr as Ident);
19    let mut input = parse_macro_input!(input as ItemImpl);
20    let func: Vec<String> = input
21        .items
22        .iter()
23        .map(|x| {
24            if let ImplItem::Fn(x) = x {
25                x.sig.ident.to_string()
26            } else {
27                String::new()
28            }
29        })
30        .collect();
31    let contains = |name: &str| func.iter().any(|x| x == name);
32    if !contains("find") {
33        input.items.push(parse_quote! {
34            pub async fn find<C>(
35                db: &C,
36                cond: actix_cloud_extra::api::Condition,
37            ) -> anyhow::Result<(Vec<#attr::Model>, u64)>
38            where
39                C: sea_orm::ConnectionTrait
40            {
41                cond.select_page(#attr::Entity::find(), db).await
42            }
43        });
44    }
45    if !contains("find_by_id") {
46        input.items.push(parse_quote! {
47            pub async fn find_by_id<C>(db: &C, id: &actix_cloud_extra::HyUuid) -> anyhow::Result<Option<#attr::Model>>
48            where
49                C: sea_orm::ConnectionTrait,
50            {
51                #attr::Entity::find_by_id(id.to_owned())
52                    .one(db)
53                    .await
54                    .map_err(Into::into)
55            }
56        });
57    }
58    if !contains("delete_all") {
59        input.items.push(parse_quote! {
60            pub async fn delete_all<C>(db: &C) -> anyhow::Result<u64>
61            where
62                C: sea_orm::ConnectionTrait,
63            {
64                #attr::Entity::delete_many()
65                    .exec(db)
66                    .await
67                    .map(|x| x.rows_affected)
68                    .map_err(Into::into)
69            }
70        });
71    }
72    if !contains("delete") {
73        input.items.push(parse_quote! {
74            pub async fn delete<C>(db: &C, id: &[actix_cloud_extra::HyUuid]) -> anyhow::Result<u64>
75            where
76                C: sea_orm::ConnectionTrait,
77            {
78                #attr::Entity::delete_many()
79                    .filter(#attr::Column::Id.is_in(actix_cloud_extra::hyuuid::uuids2strings(id)))
80                    .exec(db)
81                    .await
82                    .map(|x| x.rows_affected)
83                    .map_err(Into::into)
84            }
85        });
86    }
87    if !contains("count") {
88        input.items.push(parse_quote! {
89            pub async fn count<C>(
90                db: &C,
91                cond: actix_cloud_extra::api::Condition,
92            ) -> anyhow::Result<u64>
93            where
94                C: sea_orm::ConnectionTrait,
95            {
96                Ok(cond.build(#attr::Entity::find()).0.count(db).await?)
97            }
98        });
99    }
100
101    quote! {
102        #input
103    }
104    .into()
105}
106
107#[proc_macro_attribute]
108pub fn default_column(_: TokenStream, input: TokenStream) -> TokenStream {
109    let entity = syn::parse_macro_input!(input as syn::ItemStruct);
110
111    quote! {
112        #entity
113
114        impl actix_cloud_extra::entity::DefaultColumnTrait for Column {
115            fn get_created_at() -> impl sea_orm::ColumnTrait {
116                Self::CreatedAt
117            }
118
119            fn get_updated_at() -> impl sea_orm::ColumnTrait {
120                Self::UpdatedAt
121            }
122        }
123    }
124    .into()
125}
126
127#[cfg(feature = "seaorm")]
128/// Default timestamp generator.
129///
130/// Automatically generate `created_at` and `updated_at` on create and update.
131///
132/// # Examples
133/// ```ignore
134/// pub struct Model {
135///     ...
136///     pub created_at: i64,
137///     pub updated_at: i64,
138/// }
139///
140/// #[entity_timestamp]
141/// impl ActiveModel {}
142/// ```
143#[proc_macro_attribute]
144pub fn entity_timestamp(_: TokenStream, input: TokenStream) -> TokenStream {
145    let mut entity = syn::parse_macro_input!(input as syn::ItemImpl);
146    entity.items.push(syn::parse_quote!(
147        fn entity_timestamp(&self, e: &mut Self, insert: bool) {
148            let tm: sea_orm::ActiveValue<i64> =
149                sea_orm::ActiveValue::set(chrono::Utc::now().timestamp_millis());
150            if insert {
151                e.created_at = tm.clone();
152                e.updated_at = tm.clone();
153            } else {
154                e.updated_at = tm.clone();
155            }
156        }
157    ));
158    quote! {
159        #entity
160    }
161    .into()
162}
163
164#[cfg(feature = "seaorm")]
165/// Default id generator.
166///
167/// Automatically generate `id` on create.
168///
169/// # Examples
170/// ```ignore
171/// pub struct Model {
172///     id: i64,
173///     ...
174/// }
175///
176/// #[entity_id(rand_i64())]
177/// impl ActiveModel {}
178/// ```
179#[proc_macro_attribute]
180pub fn entity_id(attr: TokenStream, input: TokenStream) -> TokenStream {
181    let attr = syn::parse_macro_input!(attr as syn::ExprCall);
182    let mut entity = syn::parse_macro_input!(input as syn::ItemImpl);
183    entity.items.push(syn::parse_quote!(
184        fn entity_id(&self, e: &mut Self, insert: bool) {
185            if insert && e.id.is_not_set() {
186                e.id = sea_orm::ActiveValue::set(#attr);
187            }
188        }
189    ));
190    quote! {
191        #entity
192    }
193    .into()
194}
195
196#[cfg(feature = "seaorm")]
197/// Default entity behavior:
198/// - `entity_id`
199/// - `entity_timestamp`
200///
201/// # Examples
202/// ```ignore
203/// #[entity_id(rand_i64())]
204/// #[entity_timestamp]
205/// impl ActiveModel {}
206///
207/// #[entity_behavior]
208/// impl ActiveModelBehavior for ActiveModel {}
209/// ```
210#[proc_macro_attribute]
211pub fn entity_behavior(_: TokenStream, input: TokenStream) -> TokenStream {
212    let mut entity = syn::parse_macro_input!(input as syn::ItemImpl);
213
214    entity.items.push(syn::parse_quote!(
215        async fn before_save<C>(self, _: &C, insert: bool) -> Result<Self, sea_orm::DbErr>
216        where
217            C: sea_orm::ConnectionTrait,
218        {
219            let mut new = self.clone();
220            self.entity_id(&mut new, insert);
221            self.entity_timestamp(&mut new, insert);
222            Ok(new)
223        }
224    ));
225    quote! {
226        #[async_trait::async_trait]
227        #entity
228    }
229    .into()
230}
231
232#[cfg(feature = "seaorm")]
233/// Implement `into` for entity to partial entity.
234/// The fields should be exactly the same.
235///
236/// # Examples
237/// ```ignore
238/// #[partial_entity(users::Model)]
239/// #[derive(Serialize)]
240/// struct Rsp {
241///     pub id: i64,
242/// }
243///
244/// let y = users::Model {
245///     id: ...,
246///     name: ...,
247///     ...
248/// };
249/// let x: Rsp = y.into();
250/// ```
251#[proc_macro_attribute]
252pub fn partial_entity(attr: TokenStream, input: TokenStream) -> TokenStream {
253    let attr = syn::parse_macro_input!(attr as syn::ExprPath);
254    let input = syn::parse_macro_input!(input as syn::ItemStruct);
255    let name = &input.ident;
256    let mut fields = Vec::new();
257    for i in &input.fields {
258        let field_name = &i.ident;
259        fields.push(quote!(#field_name: self.#field_name,));
260    }
261
262    quote! {
263        #input
264        impl Into<#name> for #attr {
265            fn into(self) -> #name {
266                #name {
267                    #(#fields)*
268                }
269            }
270        }
271    }
272    .into()
273}