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 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 let ast = parse_macro_input!(input as DeriveInput);
118
119 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 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 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}