Skip to main content

rest_model_macro/
lib.rs

1extern crate proc_macro;
2use core::panic;
3
4use proc_macro::TokenStream;
5use quote::quote;
6use syn::{parse_macro_input, DeriveInput, Ident};
7
8/// Procedural macro to create a new struct with optional fields and copied derives
9#[proc_macro_attribute]
10pub fn rest_model(args: TokenStream, item: TokenStream) -> TokenStream {
11    // Parse the input token stream as a struct
12    let input = parse_macro_input!(item as DeriveInput);
13    let original = input.clone();
14    let struct_name = input.ident;
15
16    let mut get = false;
17    let mut get_with_id = false;
18    let mut put = false;
19    let mut patch = false;
20    let mut delete = false;
21
22    // Parse args
23    let mut db: Option<Ident> = None;
24    let mut db_name: Option<Ident> = None;
25    let mut table_name: Option<Ident> = None;
26    let parser = syn::meta::parser(|meta| {
27        if meta.path.is_ident("db") {
28            let mut i = 0;
29            meta.parse_nested_meta(|meta| {
30                let ident = meta.path.get_ident().unwrap();
31                if i == 0 {
32                    db = Some(ident.clone());
33                } else if i == 1 {
34                    db_name = Some(ident.clone());
35                } else if i == 2 {
36                    table_name = Some(ident.clone());
37                } else {
38                    return Err(meta.error("rest_model db only support 3 params"));
39                }
40                i += 1;
41                Ok(())
42            })?;
43            if i != 1 && i != 3 {
44                return Err(meta.error("rest_model db only support 1 or 3 params"));
45            }
46            Ok(())
47        } else if meta.path.is_ident("with") {
48            meta.parse_nested_meta(|meta| {
49                if meta.path.is_ident("get") {
50                    get = true;
51                    Ok(())
52                } else if meta.path.is_ident("get_with_id") {
53                    get_with_id = true;
54                    Ok(())
55                } else if meta.path.is_ident("put") {
56                    put = true;
57                    Ok(())
58                } else if meta.path.is_ident("patch") {
59                    patch = true;
60                    Ok(())
61                } else if meta.path.is_ident("delete") {
62                    delete = true;
63                    Ok(())
64                } else if meta.path.is_ident("all") {
65                    get = true;
66                    get_with_id = true;
67                    put = true;
68                    patch = true;
69                    delete = true;
70                    Ok(())
71                } else {
72                    Err(meta.error("unsupported rest_model with property"))
73                }
74            })
75        } else {
76            Err(meta.error(format!(
77                "unsupported rest_model property `{}`",
78                meta.path.get_ident().unwrap().to_string()
79            )))
80        }
81    });
82
83    parse_macro_input!(args with parser);
84
85    if db.is_none() {
86        panic!("Db must be specified");
87    }
88
89    // Generate CRUD methods based on the configuration
90    let mut methods = quote! {
91        impl rest_model::method::Init<#struct_name, #db> for #struct_name {}
92    };
93
94    if db_name.is_some() && table_name.is_some() {
95        methods.extend(quote! {
96            impl rest_model::RestModel for #struct_name {
97                fn get_db_name() -> &'static str {
98                    #db_name
99                }
100                fn get_table_name() -> &'static str {
101                    #table_name
102                }
103            }
104        });
105    }
106
107    if get_with_id {
108        methods.extend(quote! {
109            impl rest_model::method::GetWithId<#struct_name, #db> for #struct_name {}
110        });
111    }
112    if get {
113        methods.extend(quote! {
114            impl rest_model::method::Get<#struct_name, #db> for #struct_name {}
115        });
116    }
117    if put {
118        methods.extend(quote! {
119            impl rest_model::method::Put<#struct_name, #db> for #struct_name {}
120        });
121    }
122    if patch {
123        methods.extend(quote! {
124            impl rest_model::method::Patch<#struct_name, #db> for #struct_name {}
125        });
126    }
127    if delete {
128        methods.extend(quote! {
129            impl rest_model::method::Delete<#struct_name, #db> for #struct_name {}
130        });
131    }
132
133    // Generate the new struct with optional fields and the copied derives
134    let expanded = quote! {
135        #original
136        #methods
137    };
138
139    // Convert the expanded code into a TokenStream
140    TokenStream::from(expanded)
141}