actix_cloud_codegen/
lib.rs

1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2
3use proc_macro::TokenStream;
4use quote::quote;
5#[cfg(feature = "i18n")]
6use std::{collections::HashMap, env, path};
7
8#[cfg(feature = "i18n")]
9mod i18n;
10
11#[proc_macro_attribute]
12pub fn main(_: TokenStream, item: TokenStream) -> TokenStream {
13    let mut output: TokenStream = (quote! {
14        #[actix_cloud::actix_web::rt::main(system = "actix_cloud::actix_web::rt::System")]
15    })
16    .into();
17
18    output.extend(item);
19    output
20}
21
22#[cfg(feature = "i18n")]
23/// Init I18n translations.
24///
25/// This will load all translations by glob `**/*.yml` from the given path.
26///
27/// ```ignore
28/// i18n!("locales");
29/// ```
30///
31/// # Panics
32///
33/// Panics is variable `CARGO_MANIFEST_DIR` is empty.
34#[proc_macro]
35pub fn i18n(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
36    let option = match syn::parse::<i18n::Option>(input) {
37        Ok(input) => input,
38        Err(err) => return err.to_compile_error().into(),
39    };
40
41    // CARGO_MANIFEST_DIR is current build directory
42    let cargo_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is empty");
43    let current_dir = path::PathBuf::from(cargo_dir);
44    let locales_path = current_dir.join(option.locales_path);
45
46    let data = rust_i18n_support::load_locales(&locales_path.display().to_string(), |_| false);
47    let mut translation = HashMap::new();
48    for (lang, mp) in data {
49        for (k, v) in mp {
50            translation.insert(format!("{lang}.{k}"), v);
51        }
52    }
53    let code = i18n::generate_code(&translation);
54
55    if rust_i18n_support::is_debug() {
56        println!("{code}");
57    }
58
59    code.into()
60}
61
62#[cfg(feature = "seaorm")]
63/// Default timestamp generator.
64///
65/// Automatically generate `created_at` and `updated_at` on create and update.
66///
67/// # Examples
68/// ```ignore
69/// pub struct Model {
70///     ...
71///     pub created_at: i64,
72///     pub updated_at: i64,
73/// }
74///
75/// #[entity_timestamp]
76/// impl ActiveModel {}
77/// ```
78#[proc_macro_attribute]
79pub fn entity_timestamp(_: TokenStream, input: TokenStream) -> TokenStream {
80    let mut entity = syn::parse_macro_input!(input as syn::ItemImpl);
81    entity.items.push(syn::parse_quote!(
82        fn entity_timestamp(&self, e: &mut Self, insert: bool) {
83            let tm: sea_orm::ActiveValue<i64> =
84                sea_orm::ActiveValue::set(chrono::Utc::now().timestamp_millis());
85            if insert {
86                e.created_at = tm.clone();
87                e.updated_at = tm.clone();
88            } else {
89                e.updated_at = tm.clone();
90            }
91        }
92    ));
93    quote! {
94        #entity
95    }
96    .into()
97}
98
99#[cfg(feature = "seaorm")]
100/// Default id generator.
101///
102/// Automatically generate `id` on create.
103///
104/// # Examples
105/// ```ignore
106/// pub struct Model {
107///     id: i64,
108///     ...
109/// }
110///
111/// #[entity_id(rand_i64())]
112/// impl ActiveModel {}
113/// ```
114#[proc_macro_attribute]
115pub fn entity_id(attr: TokenStream, input: TokenStream) -> TokenStream {
116    let attr = syn::parse_macro_input!(attr as syn::ExprCall);
117    let mut entity = syn::parse_macro_input!(input as syn::ItemImpl);
118    entity.items.push(syn::parse_quote!(
119        fn entity_id(&self, e: &mut Self, insert: bool) {
120            if insert && e.id.is_not_set() {
121                e.id = sea_orm::ActiveValue::set(#attr);
122            }
123        }
124    ));
125    quote! {
126        #entity
127    }
128    .into()
129}
130
131#[cfg(feature = "seaorm")]
132/// Default entity behavior:
133/// - `entity_id`
134/// - `entity_timestamp`
135///
136/// # Examples
137/// ```ignore
138/// #[entity_id(rand_i64())]
139/// #[entity_timestamp]
140/// impl ActiveModel {}
141///
142/// #[entity_behavior]
143/// impl ActiveModelBehavior for ActiveModel {}
144/// ```
145#[proc_macro_attribute]
146pub fn entity_behavior(_: TokenStream, input: TokenStream) -> TokenStream {
147    let mut entity = syn::parse_macro_input!(input as syn::ItemImpl);
148
149    entity.items.push(syn::parse_quote!(
150        async fn before_save<C>(self, _: &C, insert: bool) -> Result<Self, DbErr>
151        where
152            C: ConnectionTrait,
153        {
154            let mut new = self.clone();
155            self.entity_id(&mut new, insert);
156            self.entity_timestamp(&mut new, insert);
157            Ok(new)
158        }
159    ));
160    quote! {
161        #[async_trait::async_trait]
162        #entity
163    }
164    .into()
165}
166
167#[cfg(feature = "seaorm")]
168/// Implement `into` for entity to partial entity.
169/// The fields should be exactly the same.
170///
171/// # Examples
172/// ```ignore
173/// #[partial_entity(users::Model)]
174/// #[derive(Serialize)]
175/// struct Rsp {
176///     pub id: i64,
177/// }
178///
179/// let y = users::Model {
180///     id: ...,
181///     name: ...,
182///     ...
183/// };
184/// let x: Rsp = y.into();
185/// ```
186#[proc_macro_attribute]
187pub fn partial_entity(attr: TokenStream, input: TokenStream) -> TokenStream {
188    let attr = syn::parse_macro_input!(attr as syn::ExprPath);
189    let input = syn::parse_macro_input!(input as syn::ItemStruct);
190    let name = &input.ident;
191    let mut fields = Vec::new();
192    for i in &input.fields {
193        let field_name = &i.ident;
194        fields.push(quote!(#field_name: self.#field_name,));
195    }
196
197    quote! {
198        #input
199        impl Into<#name> for #attr {
200            fn into(self) -> #name {
201                #name {
202                    #(#fields)*
203                }
204            }
205        }
206    }
207    .into()
208}