mapstruct_derive_lib/
lib.rs

1use proc_macro2::{self, TokenStream};
2use quote::quote;
3use syn::{DeriveInput, parse2};
4use crate::r#enum::MapEnum;
5
6use crate::r#struct::MapStruct;
7
8mod r#struct;
9mod r#enum;
10mod named_field_change;
11mod generic;
12mod variant;
13mod transformer;
14mod struct_change;
15mod enum_change;
16mod tuple_change;
17mod unnamed_field_change;
18
19#[macro_export]
20macro_rules! unwrap_one_variant {
21    ($expression:expr, $pattern:pat $(if $guard:expr)?, $extract:expr $(,)?) => {
22        match $expression {
23            $pattern $(if $guard)? => $extract,
24            _ => unreachable!(),
25        }
26    };
27}
28
29pub fn derive(input: TokenStream) -> TokenStream {
30    match syn_derive(input) {
31        Ok(input) => quote! {
32            #(#input)*
33        },
34        Err(err) => err.to_compile_error(),
35    }
36}
37
38fn syn_derive(input: TokenStream) -> syn::Result<Vec<DeriveInput>> {
39    let input = parse2::<DeriveInput>(input)?;
40    let attrs = input.attrs
41        .iter()
42        .filter(|attr| attr.path().is_ident("mapstruct"))
43        .map(|attr| &attr.meta)
44        .map(|meta| match meta {
45            syn::Meta::List(list) => Ok(list.tokens.clone()),
46            _ => Err(syn::Error::new_spanned(meta, "expected #[mapstruct(...)]"))
47        })
48        .collect::<Result<Vec<_>, _>>()?;
49
50    // Check if the input is a struct or an enum
51    match input.data {
52        syn::Data::Struct(_) => {
53            attrs.into_iter()
54                .map(|tokens| parse2::<MapStruct>(tokens))
55                .collect::<Result<Vec<_>, _>>()?
56                .into_iter()
57                .map(|mapstruct| mapstruct.transform(input.clone()))
58                .collect()
59        },
60        syn::Data::Enum(_) => {
61            attrs.into_iter()
62                .map(|tokens| parse2::<MapEnum>(tokens))
63                .collect::<Result<Vec<_>, _>>()?
64                .into_iter()
65                .map(|mapenum| mapenum.transform(input.clone()))
66                .collect()
67        },
68        _ => return Err(syn::Error::new_spanned(input, "expected struct or enum")),
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn test_derive_struct() {
78        let input = quote! {
79            #[mapstruct(
80                #[derive(Debug)]
81                struct Y<
82                    +'a,
83                    +T,
84                > {
85                    ~id -> x_id,
86                    ~name: &'a str,
87                    ~some: &'a str,
88                    +last_name: &'a str,
89                    -height,
90                    +t: T,
91                }
92            )]
93            struct X {
94                id: i64,
95                name: String,
96                age: i32,
97                height: f32,
98                some: String,
99            }
100        };
101        let expected = quote! {
102            #[derive(Debug)]
103            struct Y<'a, T> {
104                x_id: i64,
105                name: &'a str,
106                age: i32,
107                some: &'a str,
108                last_name: &'a str,
109                t: T
110            }
111        };
112        assert_eq!(expected.to_string(), derive(input).to_string());
113    }
114
115    #[test]
116    fn test_derive_enum_tuple() {
117        let input = quote! {
118            #[mapstruct(
119                #[derive(Debug)]
120                enum Y {
121                    -A,
122                    +D(i8),
123                }
124            )]
125            enum X {
126                A(i64),
127                B(i32),
128                C(i16),
129            }
130        };
131        let expected = quote! {
132            #[derive(Debug)]
133            enum Y {
134                B(i32),
135                C(i16),
136                D(i8)
137            }
138        };
139        assert_eq!(expected.to_string(), derive(input).to_string());
140    }
141
142    #[test]
143    fn test_derive_enum_struct() {
144        let input = quote! {
145            #[mapstruct(
146                #[derive(Debug)]
147                enum Y {
148                    -A,
149                    ~B {
150                        -name,
151                    },
152                    ~C {
153                        -name,
154                    },
155                    +D {
156                        id: i8,
157                    },
158                }
159            )]
160            enum X {
161                A {
162                    id: i64,
163                    name: String,
164                },
165                B {
166                    id: i32,
167                    name: String,
168                },
169                C {
170                    id: i16,
171                    name: String,
172                },
173            }
174        };
175        let expected = quote! {
176            #[derive(Debug)]
177            enum Y {
178                B {
179                    id: i32
180                },
181                C {
182                    id: i16
183                },
184                D {
185                    id: i8,
186                }
187            }
188        };
189        assert_eq!(expected.to_string(), derive(input).to_string());
190    }
191
192    #[test]
193    fn test_derive_enum_struct_replace() {
194        let input = quote! {
195            #[mapstruct(
196                #[derive(Debug)]
197                enum Y {
198                    ~B {
199                        -name,
200                    },
201                    ~C {
202                        -name,
203                    },
204                    ~A -> D {
205                        ~id: i8,
206                        -name,
207                    },
208                }
209            )]
210            enum X {
211                A {
212                    id: i64,
213                    name: String,
214                },
215                B {
216                    id: i32,
217                    name: String,
218                },
219                C {
220                    id: i16,
221                    name: String,
222                },
223            }
224        };
225        let expected = quote! {
226            #[derive(Debug)]
227            enum Y {
228                D {
229                    id: i8
230                },
231                B {
232                    id: i32
233                },
234                C {
235                    id: i16
236                }
237            }
238        };
239        assert_eq!(expected.to_string(), derive(input).to_string());
240    }
241
242    #[test]
243    fn test_derive_enum_tuple_replace() {
244        let input = quote! {
245            #[mapstruct(
246                #[derive(Debug)]
247                pub enum Y {
248                    A(i64),
249                    B(i32),
250                    C(i16),
251                    +D(i8),
252                }
253            )]
254            enum X {
255                A {
256                    id: i64,
257                    name: String,
258                },
259                B {
260                    id: i32,
261                    name: String,
262                },
263                C {
264                    id: i16,
265                    name: String,
266                },
267            }
268        };
269        let expected = quote! {
270            #[derive(Debug)]
271            pub enum Y {
272                A(i64),
273                B(i32),
274                C(i16),
275                D(i8)
276            }
277        };
278        assert_eq!(expected.to_string(), derive(input).to_string());
279    }
280
281    #[test]
282    fn test_derive_enum_tuple_change() {
283        let input = quote! {
284            #[mapstruct(
285                #[derive(Debug)]
286                pub enum Y {
287                    ~A(_, _, _, +i64),
288                    ~B(_, _, ~i128),
289                    ~C(_, _, ~u16, +u8),
290                    +E(i8, i16, i32, i64),
291                }
292            )]
293            enum X {
294                A(i8, i16, i32),
295                B(i32, i64, i16),
296                C(i16, i8, i32),
297                D(i8, i16, i32, i64),
298            }
299        };
300        let expected = quote! {
301            #[derive(Debug)]
302            pub enum Y {
303                A(i8, i16, i32, i64),
304                B(i32, i64, i128),
305                C(i16, i8, u16, u8),
306                D(i8, i16, i32, i64),
307                E(i8, i16, i32, i64)
308            }
309        };
310        assert_eq!(expected.to_string(), derive(input).to_string());
311    }
312}