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#[cfg(feature = "seaorm")]
108/// Default timestamp generator.
109///
110/// Automatically generate `created_at` and `updated_at` on create and update.
111///
112/// # Examples
113/// ```ignore
114/// pub struct Model {
115///     ...
116///     pub created_at: DateTime,
117///     pub updated_at: DateTime,
118/// }
119///
120/// #[entity_timestamp]
121/// impl ActiveModel {}
122/// ```
123#[proc_macro_attribute]
124pub fn entity_timestamp(_: TokenStream, input: TokenStream) -> TokenStream {
125    let mut entity = syn::parse_macro_input!(input as syn::ItemImpl);
126    entity.items.push(syn::parse_quote!(
127        fn entity_timestamp(&self, e: &mut Self, insert: bool) {
128            let tm: sea_orm::ActiveValue<DateTime> =
129                sea_orm::ActiveValue::set(chrono::Utc::now().naive_utc());
130            if insert {
131                e.created_at = tm.clone();
132                e.updated_at = tm.clone();
133            } else {
134                e.updated_at = tm.clone();
135            }
136        }
137    ));
138    quote! {
139        #entity
140
141        impl actix_cloud_extra::entity::DefaultColumnTrait for Column {
142            fn get_created_at() -> impl sea_orm::ColumnTrait {
143                Self::CreatedAt
144            }
145
146            fn get_updated_at() -> impl sea_orm::ColumnTrait {
147                Self::UpdatedAt
148            }
149        }
150    }
151    .into()
152}
153
154#[cfg(feature = "seaorm")]
155/// Default id generator.
156///
157/// Automatically generate `id` on create.
158///
159/// # Examples
160/// ```ignore
161/// pub struct Model {
162///     id: i64,
163///     ...
164/// }
165///
166/// #[entity_id(rand_i64())]
167/// impl ActiveModel {}
168/// ```
169#[proc_macro_attribute]
170pub fn entity_id(attr: TokenStream, input: TokenStream) -> TokenStream {
171    let attr = syn::parse_macro_input!(attr as syn::ExprCall);
172    let mut entity = syn::parse_macro_input!(input as syn::ItemImpl);
173    entity.items.push(syn::parse_quote!(
174        fn entity_id(&self, e: &mut Self, insert: bool) {
175            if insert && e.id.is_not_set() {
176                e.id = sea_orm::ActiveValue::set(#attr);
177            }
178        }
179    ));
180    quote! {
181        #entity
182    }
183    .into()
184}
185
186#[cfg(feature = "seaorm")]
187/// Default entity behavior:
188/// - `entity_id`
189/// - `entity_timestamp`
190///
191/// # Examples
192/// ```ignore
193/// #[entity_id(rand_i64())]
194/// #[entity_timestamp]
195/// impl ActiveModel {}
196///
197/// #[entity_behavior]
198/// impl ActiveModelBehavior for ActiveModel {}
199/// ```
200#[proc_macro_attribute]
201pub fn entity_behavior(_: TokenStream, input: TokenStream) -> TokenStream {
202    let mut entity = syn::parse_macro_input!(input as syn::ItemImpl);
203
204    entity.items.push(syn::parse_quote!(
205        async fn before_save<C>(self, _: &C, insert: bool) -> Result<Self, sea_orm::DbErr>
206        where
207            C: sea_orm::ConnectionTrait,
208        {
209            let mut new = self.clone();
210            self.entity_id(&mut new, insert);
211            self.entity_timestamp(&mut new, insert);
212            Ok(new)
213        }
214    ));
215    quote! {
216        #[async_trait::async_trait]
217        #entity
218    }
219    .into()
220}
221
222#[cfg(feature = "seaorm")]
223/// Implement `into` for entity to partial entity.
224/// The fields should be exactly the same.
225///
226/// # Examples
227/// ```ignore
228/// #[partial_entity(users::Model)]
229/// #[derive(Serialize)]
230/// struct Rsp {
231///     pub id: i64,
232/// }
233///
234/// let y = users::Model {
235///     id: ...,
236///     name: ...,
237///     ...
238/// };
239/// let x: Rsp = y.into();
240/// ```
241#[proc_macro_attribute]
242pub fn partial_entity(attr: TokenStream, input: TokenStream) -> TokenStream {
243    let attr = syn::parse_macro_input!(attr as syn::ExprPath);
244    let input = syn::parse_macro_input!(input as syn::ItemStruct);
245    let name = &input.ident;
246    let mut fields = Vec::new();
247    for i in &input.fields {
248        let field_name = &i.ident;
249        fields.push(quote!(#field_name: self.#field_name,));
250    }
251
252    quote! {
253        #input
254        impl Into<#name> for #attr {
255            fn into(self) -> #name {
256                #name {
257                    #(#fields)*
258                }
259            }
260        }
261    }
262    .into()
263}