derive_from_ext/
lib.rs

1use std::{collections::HashMap};
2use proc_macro::{self, TokenStream};
3use proc_macro2::{Ident, Span};
4use quote::{quote, ToTokens};
5use syn::{parse_macro_input, DeriveInput, Token, punctuated::Punctuated, Path, Lit};
6use syn_unnamed_struct::{Meta, MetaValue, NestedMeta};
7
8const INTO_METHOD: &str = "core::convert::Into::into";
9const SKIP_METHOD: &str = "core::default::Default::default()";
10
11#[derive(Default)]
12struct FieldOverridesArgs {
13    pub map: Option<String>,
14    pub rename: Option<String>,
15    pub skip: bool,
16    pub default: Option<String>,
17}
18
19#[derive(Default)]
20struct FieldArgs {
21    pub map: Option<String>,
22    pub rename: Option<String>,
23    pub skip: bool,
24    pub default: Option<String>,
25    pub overrides: HashMap<String, FieldOverridesArgs>,
26}
27
28fn extract_meta_str(expr: &MetaValue) -> String {
29    match expr {
30        MetaValue::Lit(value) => {
31            match &value {
32                Lit::Str(value_str) => value_str.value(),
33                _ => panic!("Only strings supported: {}", value.to_token_stream())
34            }
35        },
36        _ => panic!("Expected literal key name: {}", expr.into_token_stream())
37    }
38}
39
40fn extract_meta_bool(expr: &MetaValue) -> bool {
41    match expr {
42        MetaValue::Lit(value) => {
43            match &value {
44                Lit::Bool(value_bool) => value_bool.value(),
45                _ => panic!("Only bools supported: {}", value.to_token_stream())
46            }
47        },
48        _ => panic!("Expected literl key name: {}", expr.to_token_stream())
49    }
50}
51
52fn extract_field_override_arg(custom_expr: &MetaValue) -> FieldOverridesArgs {
53    let mut overrides = FieldOverridesArgs::default();
54    
55    match custom_expr {
56        MetaValue::UnnamedMetaList(subobj) => {
57            for field in &subobj.nested {
58                match field {
59                    NestedMeta::Meta(meta) => {
60                        match meta {
61                            Meta::Path(path) => {
62                                match path.to_token_stream().to_string().as_str() {
63                                    "skip" => {
64                                        overrides.skip = true;
65                                    },
66                                    _ => panic!("Unrecognised bool property")
67                                }
68                            },
69                            Meta::NameValue(pair) => {
70                                let subobj_field_name = pair.path.to_token_stream().to_string();
71                                
72                                match subobj_field_name.as_str() {
73                                    "map" => {
74                                        overrides.map = Some(extract_meta_str(&pair.value));
75                                    },
76                                    "rename" => {
77                                        overrides.rename = Some(extract_meta_str(&pair.value));
78                                    },
79                                    "default" => {
80                                        overrides.default = Some(extract_meta_str(&pair.value));
81                                    },
82                                    "skip" => {
83                                        overrides.skip = extract_meta_bool(&pair.value)
84                                    },
85                                    _ => panic!("Could not match override property: {}", subobj_field_name)
86                                }
87                            },
88                            _ => panic!("Each override should be a name / value pair or single truthy value")
89                        }
90                    },
91                    _ => panic!("Expects named fields in each override")
92                }
93            }
94        },
95        _ => panic!("Each override value should be an unamed struct")
96    }
97    
98    overrides
99}
100
101#[proc_macro_derive(From, attributes(from))]
102pub fn derive(input: TokenStream) -> TokenStream {
103    let input = parse_macro_input!(input);
104    let DeriveInput { ident, attrs, data, .. } = &input;
105
106    //fetch all of the types from the struct attribute macros
107    let type_names = attrs.iter().filter(|a| a.path.is_ident("from")).flat_map(|attr| {
108        attr.parse_args_with(Punctuated::<Path, Token![,]>::parse_terminated).expect("Could not parse 'from' attribute")
109    }).map(|path| {
110        (path.to_token_stream().to_string(), path)
111    }).collect::<HashMap<String, Path>>();
112
113    let obj = match data {
114        syn::Data::Struct(obj) => obj,
115        _ => panic!("Only structs supported in From macro")
116    };
117
118    //determine the field-specific properties
119    let field_objs = obj.fields.iter().map(|field| {
120        let field_name = field.ident.as_ref().expect("Structs must contain named fields").clone();
121        let mut props = FieldArgs::default();
122
123        field.attrs.iter().filter(|a| a.path.is_ident("from")).flat_map(|attr| {
124            attr.parse_args_with(<Punctuated<Meta, Token![,]>>::parse_terminated).expect("Could not parse 'from' attribute")
125        }).for_each(|meta| {
126            match meta {
127                Meta::Path(path) => {
128                    match path.to_token_stream().to_string().as_str() {
129                        "skip" => {
130                            props.skip = true;
131                        },
132                        _ => panic!("Unrecognised bool property")
133                    }
134                },
135                Meta::NameValue(pair) => {
136                    match pair.path.to_token_stream().to_string().as_str() {
137                        "map" => {
138                            props.map = Some(extract_meta_str(&pair.value))
139                        },
140                        "rename" => {
141                            props.rename = Some(extract_meta_str(&pair.value))
142                        },
143                        "default" => {
144                            props.default = Some(extract_meta_str(&pair.value))
145                        },
146                        "skip" => {
147                            props.skip = extract_meta_bool(&pair.value)
148                        },
149                        "overrides" => {
150                            match pair.value {
151                                MetaValue::UnnamedMetaList(obj) => {
152                                    for nested_field in obj.nested {
153                                        match nested_field {
154                                            NestedMeta::Meta(meta) => {
155                                                match meta {
156                                                    Meta::NameValue(pair) => {
157                                                        let field_name = pair.path.get_inner().to_token_stream().to_string();
158        
159                                                        if !type_names.contains_key(&field_name) {
160                                                            panic!("Type does not exist for override: {}", field_name);
161                                                        }
162
163                                                        let overrides = extract_field_override_arg(&pair.value);
164                                                        props.overrides.insert(field_name, overrides);
165                                                    },
166                                                    _ => panic!("Each override should list a type and properties to override")
167                                                }
168                                            },
169                                            _ => panic!("Each override should list a type and properties to override")
170                                        }
171                                    }
172                                },
173                                _ => panic!("Overrides must be an unnamed meta list")
174                            }
175                        },
176                        _ => panic!("Unrecognised key value pair")
177                    }
178                },
179                _ => panic!("Expected name value pair")
180            }
181        });
182
183        (field_name, props)
184    }).collect::<Vec<_>>();
185
186    //determine how each field will map
187    let mut output = proc_macro2::TokenStream::new();
188
189    for (type_str, type_name) in type_names {
190        let fields = field_objs.iter().map(|(field_name, field_obj)| {
191            let mut map = field_obj.map.clone();
192            let mut rename = field_obj.rename.clone();
193            let mut skip = field_obj.skip;
194            let mut default = field_obj.default.clone();
195
196            //override type-specific and field-specific settings
197            if let Some(type_override) = field_obj.overrides.get(&type_str) {
198                if type_override.map.is_some() {
199                    map = type_override.map.clone();
200                }
201                
202                if type_override.rename.is_some() {
203                    rename = type_override.rename.clone();
204                }
205                
206                if type_override.default.is_some() {
207                    default = type_override.default.clone();
208                }
209                
210                if type_override.skip {
211                    skip = type_override.skip;
212                }
213            }
214
215            let target_value = {
216                if skip {
217                    let method_name: proc_macro2::TokenStream = default.unwrap_or_else(|| {
218                        SKIP_METHOD.to_string()
219                    }).parse().expect("Could not parse skip method");
220
221                    quote!(#method_name)
222                } else {
223                    let target_name = rename.map(|name| {
224                        Ident::new(&name, Span::call_site())
225                    }).unwrap_or_else(|| {
226                        field_name.clone()
227                    });
228
229                    let method_name: proc_macro2::TokenStream = map.unwrap_or_else(|| {
230                        INTO_METHOD.to_string()
231                    }).parse().expect("Could not parse map method");
232
233                    quote!(#method_name(obj.#target_name))
234                }
235            };
236
237            quote!(#field_name: #target_value)
238        }).collect::<Vec<_>>();
239
240        let type_impl = quote! {
241            impl ::std::convert::From<#type_name> for #ident {
242                fn from(obj: #type_name) -> Self {
243                    Self {
244                        #(#fields),*
245                    }
246                }
247            }
248        };
249
250        output.extend(type_impl);
251    }
252
253    output.into()
254}