valu3_derive/
lib.rs

1extern crate proc_macro;
2use proc_macro::TokenStream;
3use quote::quote;
4use syn::{parse_macro_input, Data, DeriveInput, Fields, Generics, Variant};
5
6#[proc_macro_derive(ToValue, attributes(attr))]
7pub fn to_value_derive(input: TokenStream) -> TokenStream {
8    let input = parse_macro_input!(input as DeriveInput);
9
10    let name = input.ident;
11    let generics = input.generics;
12
13    let to_value_impl = match input.data {
14        Data::Struct(data) => to_value_struct_impl(name, generics, data.fields),
15        Data::Enum(data) => to_value_enum_impl(name, generics, data.variants),
16        Data::Union(_) => panic!("ToValueBehavior cannot be derived for unions"),
17    };
18
19    let expanded = quote! {
20        #to_value_impl
21    };
22
23    TokenStream::from(expanded)
24}
25
26fn to_value_struct_impl(
27    name: syn::Ident,
28    generics: Generics,
29    fields: Fields,
30) -> proc_macro2::TokenStream {
31    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
32
33    let field_transforms = match fields {
34        Fields::Named(fields) => fields
35            .named
36            .iter()
37            .map(|field| {
38                let name = match field.ident.as_ref() {
39                    Some(name) => name,
40                    None => panic!("ToValueBehavior cannot be derived for unnamed fields"),
41                };
42                let field_name = format!("{}", name);
43                quote! {
44                    map.insert(#field_name.to_string(), self.#name.clone().into());
45                }
46            })
47            .collect::<Vec<_>>(),
48        Fields::Unnamed(fields) => fields
49            .unnamed
50            .iter()
51            .enumerate()
52            .map(|(index, _field)| {
53                let index = syn::Index::from(index);
54                quote! {
55                    Value::from(self.#index.clone())
56                }
57            })
58            .collect::<Vec<_>>(),
59        Fields::Unit => {
60            return quote! {
61                impl #impl_generics ToValueBehavior for  #name #ty_generics #where_clause {
62                    fn to_value(&self) -> Value {
63                        Value::Null
64                    }
65                }
66            }
67        }
68    };
69
70    quote! {
71        impl #impl_generics ToValueBehavior  for #name #ty_generics #where_clause {
72            fn to_value(&self) -> Value {
73                let mut map: std::collections::HashMap<String, Value>= std::collections::HashMap::new();
74                #(#field_transforms)*
75                Value::from(map)
76            }
77        }
78    }
79}
80
81fn to_value_enum_impl(
82    name: syn::Ident,
83    generics: Generics,
84    variants: syn::punctuated::Punctuated<Variant, syn::Token![,]>,
85) -> proc_macro2::TokenStream {
86    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
87    // Ensure we only support unit variants for now. If a variant carries data,
88    // fail early with a clear message so the user knows the derive is not
89    // implemented for data-carrying variants yet.
90    let mut arms = Vec::new();
91    for variant in variants.iter() {
92        let variant_name = &variant.ident;
93        match &variant.fields {
94            Fields::Unit => {
95                arms.push(quote! {
96                    #name::#variant_name => Value::from(stringify!(#variant_name)),
97                });
98            }
99            _ => panic!("ToValue cannot be derived for enums with data-carrying variants (tuple or struct variants)"),
100        }
101    }
102
103    quote! {
104        impl #impl_generics ToValueBehavior for #name #ty_generics #where_clause {
105            fn to_value(&self) -> Value {
106                match self {
107                    #(#arms)*
108                }
109            }
110        }
111    }
112}
113
114#[proc_macro_derive(FromValue)]
115pub fn from_value_derive(input: TokenStream) -> TokenStream {
116    // Parse a `DeriveInput` AST from the input tokens.
117    let ast = parse_macro_input!(input as DeriveInput);
118
119    // Get the name and fields of the struct being derived.
120    let target_name = &ast.ident;
121    let target_generics = &ast.generics;
122    let (impl_generics, ty_generics, where_clause) = target_generics.split_for_impl();
123
124    match ast.data {
125        Data::Struct(data_struct) => {
126            // Define a new implementation of the `FromValueBehavior` trait for the struct.
127            let mut field_names = Vec::new();
128            let mut from_value_exprs = Vec::new();
129
130            if let Fields::Named(fields) = data_struct.fields {
131                for field in fields.named.iter() {
132                    let field_name = match field.ident.as_ref() {
133                        Some(name) => name,
134                        None => panic!(
135                            "Can only derive FromValueBehavior for a struct with named fields."
136                        ),
137                    };
138                    let field_type = &field.ty;
139
140                    field_names.push(field_name.clone());
141
142                    from_value_exprs.push(quote! {
143                        #field_name: {
144                            let item = match map.get(stringify!(#field_name)) {
145                                Some(item) => item.clone(),
146                                None => return None,
147                            };
148                            match <#field_type as FromValueBehavior>::from_value(item) {
149                                Some(item) => item,
150                                None => return None,
151                            }
152                        }
153                    });
154                }
155            } else {
156                panic!("Can only derive FromValueBehavior for a struct with named fields.");
157            }
158
159            let expanded = quote! {
160                impl #impl_generics FromValueBehavior for #target_name #ty_generics #where_clause {
161                    type Item = Self;
162
163                    fn from_value(value: Value) -> Option<Self> {
164                        if let Value::Object(map) = value {
165                            Some(
166                                Self {
167                                    #(#from_value_exprs),*
168                                }
169                            )
170                        } else {
171                            None
172                        }
173                    }
174                }
175            };
176
177            TokenStream::from(expanded)
178        }
179        Data::Enum(data_enum) => {
180            let variants = data_enum.variants;
181
182            let mut variant_names = Vec::new();
183
184            // Only support unit variants for now. If any variant has fields,
185            // fail early with a clear message to the user.
186            for variant in variants.iter() {
187                match &variant.fields {
188                    Fields::Unit => {
189                        let variant_name = &variant.ident;
190                        variant_names.push(variant_name.clone());
191                    }
192                    _ => panic!("FromValue cannot be derived for enums with data-carrying variants (tuple or struct variants)"),
193                }
194            }
195
196            let expanded = quote! {
197                impl #impl_generics PrimitiveType for #target_name #ty_generics #where_clause {}
198
199                impl #impl_generics FromValueBehavior for #target_name #ty_generics #where_clause {
200                    type Item = Self;
201
202                    fn from_value(value: Value) -> Option<Self> {
203                        match value {
204                            Value::String(value) => {
205                                match value.as_str() {
206                                    #(
207                                        v if v == stringify!(#variant_names) => Some(#target_name::#variant_names),
208                                    )*
209                                    _ => None,
210                                }
211                            },
212                            _ => None,
213                        }
214                    }
215                }
216            };
217
218            TokenStream::from(expanded)
219        }
220        _ => panic!("Can only derive FromValueBehavior for a struct."),
221    }
222}
223
224#[proc_macro_derive(ToJson)]
225pub fn to_json_derive(input: TokenStream) -> TokenStream {
226    let ast = parse_macro_input!(input as DeriveInput);
227    let name = &ast.ident;
228    let generics = &ast.generics;
229    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
230
231    let gen = quote! {
232        impl #impl_generics ToJsonBehavior for #name #ty_generics #where_clause {
233            fn to_json(&self) -> String {
234                self.to_value().to_string()
235            }
236        }
237    };
238
239    gen.into()
240}
241
242#[proc_macro_derive(ToYaml)]
243pub fn to_yaml_derive(input: TokenStream) -> TokenStream {
244    let input = parse_macro_input!(input as DeriveInput);
245    let name = input.ident;
246    let generics = input.generics;
247    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
248
249    let expanded = quote! {
250        impl #impl_generics ToYamlBehavior for #name  #ty_generics #where_clause {
251            fn to_yaml(&self) -> String {
252                let value = self.to_value();
253                value.to_yaml()
254            }
255        }
256    };
257
258    TokenStream::from(expanded)
259}