struct_auto_from/lib.rs
1use std::collections::HashMap;
2
3use darling::FromField;
4use proc_macro::TokenStream;
5use proc_macro2::{Ident, Span};
6use quote::quote;
7use syn::{parse_macro_input, Expr, Field, ItemStruct, Meta, MetaList};
8
9/// Auto implement `From` trait.
10///
11/// When specifying conversion, all fields in the receiving struct type
12/// must either be defined in the sender, or have their default values
13/// defined on the receiver.
14///
15/// Default value attribute lets you override data from sender.
16///
17/// <br>
18///
19/// # Examples
20/// ```
21/// use struct_auto_from::auto_from;
22///
23/// #[derive(Clone)]
24/// struct Model1 {
25/// id: i32,
26/// name: String,
27/// attrs: Vec<String>,
28/// }
29///
30/// #[auto_from(Model1)]
31/// struct Model2 {
32/// id: i32,
33/// name: String,
34/// attrs: Vec<String>,
35/// }
36///
37///
38/// let m1: Model1 = Model1 {
39/// id: 0,
40/// name: "M".into(),
41/// attrs: vec![],
42/// };
43/// let m2: Model2 = m1.clone().into();
44///
45/// assert_eq!(m1.id, m2.id);
46/// assert_eq!(m1.name, m2.name);
47/// assert_eq!(m1.attrs, m2.attrs);
48/// ```
49///
50/// Using the default values
51///
52/// ```
53/// use std::collections::HashMap;
54/// use struct_auto_from::auto_from;
55///
56/// #[derive(Clone)]
57/// struct Model1 {
58/// id: i32,
59/// name: String,
60/// attrs: Vec<String>,
61/// }
62///
63/// #[auto_from(Model1)]
64/// struct Model2 {
65/// #[auto_from_attr(default_value = -1)]
66/// id: i32,
67/// name: String,
68/// attrs: Vec<String>,
69/// #[auto_from_attr(default_value = Default::default())]
70/// metadata: HashMap<String, String>,
71/// }
72///
73///
74/// let m1: Model1 = Model1 {
75/// id: 0,
76/// name: "M".into(),
77/// attrs: vec![],
78/// };
79/// let m2: Model2 = m1.clone().into();
80///
81/// assert_eq!(-1, m2.id);
82/// assert_eq!(m1.name, m2.name);
83/// assert_eq!(m1.attrs, m2.attrs);
84/// assert!(m2.metadata.is_empty());
85/// ```
86///
87#[proc_macro_attribute]
88pub fn auto_from(attrs: TokenStream, input: TokenStream) -> TokenStream {
89 let from = parse_macro_input!(attrs as Ident);
90
91 let into = parse_macro_input!(input as ItemStruct);
92 let ImplData {
93 raw_into,
94 into,
95 fields,
96 default_fields,
97 default_values,
98 } = ImplData::from_parsed_input(into);
99
100 let tokens = quote! {
101 #raw_into
102
103 impl From<#from> for #into {
104 fn from(value: #from) -> Self {
105 Self {
106 #(
107 #fields: value.#fields
108 ),*
109 ,
110 #(
111 #default_fields: #default_values
112 ),*
113 }
114 }
115 }
116 };
117
118 tokens.into()
119}
120
121struct ImplData {
122 raw_into: ItemStruct,
123 into: Ident,
124 fields: Vec<Ident>,
125 default_fields: Vec<Ident>,
126 default_values: Vec<Expr>,
127}
128
129impl ImplData {
130 fn from_parsed_input(input: ItemStruct) -> Self {
131 let mut raw_into = input.clone();
132 let into = input.ident;
133 let (default_fields, default_values): (Vec<_>, Vec<_>) =
134 Self::extract_defaults_from_input(&mut raw_into)
135 .into_iter()
136 .unzip();
137 let fields = input
138 .fields
139 .into_iter()
140 .filter_map(|f| f.ident)
141 .filter(|i| !default_fields.contains(i))
142 .collect();
143
144 Self {
145 raw_into,
146 into,
147 fields,
148 default_fields,
149 default_values,
150 }
151 }
152
153 fn extract_defaults_from_input(input: &mut ItemStruct) -> HashMap<Ident, Expr> {
154 let mut defaults = HashMap::new();
155
156 for field in input.fields.iter_mut() {
157 let attrs = AutoFromAttr::from_field(field).unwrap();
158
159 if let (Some(ident), Some(default_value)) = (&mut field.ident, attrs.default_value) {
160 defaults.insert(ident.clone(), default_value);
161 Self::remove_attrs(field);
162 }
163 }
164
165 defaults
166 }
167
168 fn remove_attrs(field: &mut Field) {
169 field.attrs.retain(|a| {
170 let Meta::List(MetaList { path, .. }) = &a.meta else {
171 return false
172 };
173
174 !path.is_ident(&Ident::new("auto_from_attr", Span::call_site()))
175 })
176 }
177}
178
179#[derive(FromField, Default, Debug)]
180#[darling(default, attributes(auto_from_attr))]
181struct AutoFromAttr {
182 default_value: Option<Expr>,
183}