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 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 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 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 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}