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