serde_validate_macro/
lib.rs

1/*
2 * serde-validate-macro - A procedural macro that validates the deserialization of a struct
3 *
4 * Copyright (C) 2024 Lucas M. de Jong Larrarte
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20//! # serde-validate-macro
21//!
22//! This crate provides the `validate_deser` procedural serde-validate-macro for the `serde-validate` crate.
23//!
24//! Users should prefer using the `serde-validate` crate.
25
26
27extern crate proc_macro;
28use proc_macro::TokenStream;
29use proc_macro2::Ident;
30use syn::{parse_macro_input, DeriveInput, Data, Fields, Field, Index, Variant, Generics, GenericParam};
31use quote::{quote, ToTokens};
32use syn::punctuated::Punctuated;
33use syn::token::Comma;
34
35
36/// Attribute serde-validate-macro to derive deserialization with validation for a struct or enum.
37///
38/// This serde-validate-macro generates a helper struct to deserialize the original struct or enum and
39/// then validates the deserialized data using the `serde_validate::Validate` trait. If validation fails,
40/// a deserialization error is returned.
41#[proc_macro_attribute]
42pub fn validate_deser(_args: TokenStream, input: TokenStream) -> TokenStream {
43    let input = parse_macro_input!(input as DeriveInput);
44
45    let name = &input.ident;
46    
47    let generics = &input.generics;
48
49    let helper_name = Ident::new(&format!("__ValidDeserialize{name}"), name.span());
50
51    let HelperData { helper_def, init_from_helper } = match input.data {
52        Data::Struct(ref data) => match data.fields {
53            Fields::Named(ref fields) => build_named_struct(&name, &helper_name, &generics, &fields.named),
54            Fields::Unnamed(ref fields) => build_unnamed_struct(&name, &helper_name, &generics, &fields.unnamed),
55            Fields::Unit => build_unit_struct(&name, &helper_name),
56        }
57        Data::Enum(ref data) => build_enum(&name, &helper_name, &generics, &data.variants),
58        Data::Union(_) => {unimplemented!()}
59    };
60    
61    let generic_params = generics.params.to_token_stream();
62    let extra_where_clause: Vec<_> = generics.params.iter().filter_map(|p| match p {
63        GenericParam::Type(p) => {
64            let p = &p.ident;
65            Some(quote! { #p : serde::Deserialize<'__de> })
66        }
67        _ => None,
68    }).collect();
69    let where_clause = match generics.where_clause {
70        None => quote! {
71            where #(#extra_where_clause,)*
72        },
73        Some(ref clause) => {
74            let predicates = clause.predicates.iter().map(|p| p.to_token_stream());
75            quote! {
76                where #(#predicates,)* #(#extra_where_clause,)*
77            }
78        }
79    };
80    let simple_gen_params = generics.params.iter().map(|p| match p {
81        GenericParam::Type(p) => {
82            let p = &p.ident;
83            quote! { #p }
84        }
85        GenericParam::Lifetime(p) => {
86            let p = &p.lifetime;
87            quote! { #p }
88        },
89        GenericParam::Const(p) => {
90            let p = &p.ident;
91            quote! { #p }
92        },
93    });
94
95    let tokens = quote! {
96        #input
97
98        #[derive(serde::Deserialize)]
99        #helper_def
100
101        impl <'__de, #generic_params> serde::Deserialize<'__de> for #name<#(#simple_gen_params,)*> #where_clause {
102            fn deserialize<__D>(deserializer: __D) -> Result<Self, __D::Error>
103            where
104                __D: serde::Deserializer<'__de>
105            {
106                let helper = #helper_name::deserialize(deserializer)?;
107                let instance = #init_from_helper;
108                instance.validated().map_err(serde::de::Error::custom)
109            }
110        }
111
112    };
113
114    tokens.into()
115}
116
117
118struct HelperData {
119    helper_def: proc_macro2::TokenStream,
120    init_from_helper: proc_macro2::TokenStream,
121}
122
123fn build_named_struct(name: &Ident, helper_name: &Ident, generics: &Generics, fields: &Punctuated<Field, Comma>) -> HelperData {
124    let helper_def = named_def_full(helper_name, generics, fields);
125
126    let helper_def = quote! {
127        struct #helper_def
128    };
129
130    let init_from_helper = init_from_named(name, fields);
131
132    HelperData {
133        helper_def,
134        init_from_helper
135    }
136}
137
138fn named_def_full(name: &Ident, generics: &Generics, fields: &Punctuated<Field, Comma>) -> proc_macro2::TokenStream {
139    let generic_params = generics.params.to_token_stream();
140    let where_clause = generics.where_clause.to_token_stream();
141    let fields = fields.to_token_stream();
142    quote! {
143        #name<#generic_params> #where_clause {
144            #fields
145        }
146    }
147}
148
149fn init_from_named(name: &Ident, fields: &Punctuated<Field, Comma>) -> proc_macro2::TokenStream {
150    let init_fields = fields.iter().map(|field| {
151        let name = &field.ident;
152        quote! { #name: helper.#name }
153    });
154
155    quote! {
156        #name {
157            #( #init_fields ),*
158        }
159    }
160}
161
162fn build_unnamed_struct(name: &Ident, helper_name: &Ident, generics: &Generics, fields: &Punctuated<Field, Comma>) -> HelperData {
163    let helper_def = unnamed_def_full(helper_name, generics, fields);
164
165    let helper_def = quote! {
166        struct #helper_def
167    };
168
169    let init_from_helper = init_from_unnamed(name, fields);
170
171    HelperData {
172        helper_def,
173        init_from_helper
174    }
175}
176
177fn unnamed_def_full(name: &Ident, generics: &Generics, fields: &Punctuated<Field, Comma>) -> proc_macro2::TokenStream {
178    let generic_params = generics.params.to_token_stream();
179    let where_clause = generics.where_clause.to_token_stream();
180    let fields = fields.to_token_stream();
181    quote! {
182        #name<#generic_params>(#fields) #where_clause;
183    }
184}
185
186fn init_from_unnamed(name: &Ident, fields: &Punctuated<Field, Comma>) -> proc_macro2::TokenStream {
187    let init_fields = fields.iter().enumerate().map(|(i, _)| {
188        let index = Index::from(i);
189        quote! { helper.#index }
190    });
191
192    quote! {
193        #name(#( #init_fields ),*)
194    }
195}
196
197fn build_unit_struct(name: &Ident, helper_name: &Ident) -> HelperData {
198    let helper_def = quote! {
199        struct #helper_name;
200    };
201
202    let init_from_helper = quote! {
203        #name
204    };
205
206    HelperData {
207        helper_def,
208        init_from_helper
209    }
210}
211
212fn build_enum(name: &Ident, helper_name: &Ident, generics: &Generics, variants: &Punctuated<Variant, Comma>) -> HelperData {
213    let helper_def = enum_def(helper_name, generics, variants);
214
215    let init_from_helper = init_from_enum(name, helper_name, variants);
216
217    HelperData {
218        helper_def,
219        init_from_helper
220    }
221}
222
223fn enum_def(name: &Ident, generics: &Generics, variants: &Punctuated<Variant, Comma>) -> proc_macro2::TokenStream {
224    let generic_params = generics.params.to_token_stream();
225    let where_clause = generics.where_clause.to_token_stream();
226    let variants = variants.iter().map(|variant| {
227        let name = &variant.ident;
228        match variant.fields {
229            Fields::Named(ref fields) => named_def(name, &fields.named),
230            Fields::Unnamed(ref fields) => unnamed_def(name, &fields.unnamed),
231            Fields::Unit => quote! { #name },
232        }
233    });
234    quote! {
235        enum #name<#generic_params> #where_clause {
236            #( #variants ),*
237        }
238    }
239}
240
241fn init_from_enum(name: &Ident, helper_name: &Ident, variants: &Punctuated<Variant, Comma>) -> proc_macro2::TokenStream {
242    let init_variants = variants.iter().map(|variant| {
243        let variant_name = &variant.ident;
244        match variant.fields {
245            Fields::Named(ref fields) => init_enum_from_named(name, helper_name, variant_name, &fields.named),
246            Fields::Unnamed(ref fields) => init_enum_from_unnamed(name, helper_name, variant_name, &fields.unnamed),
247            Fields::Unit => init_enum_from_unit(name, helper_name, variant_name),
248        }
249    });
250    quote! {
251        match helper {
252            #( #init_variants ),*
253        }
254    }
255}
256
257fn named_def(name: &Ident, fields: &Punctuated<Field, Comma>) -> proc_macro2::TokenStream {
258    let fields = fields.to_token_stream();
259    quote! {
260        #name {
261            #fields
262        }
263    }
264}
265
266fn unnamed_def(name: &Ident, fields: &Punctuated<Field, Comma>) -> proc_macro2::TokenStream {
267    let fields = fields.to_token_stream();
268    quote! {
269        #name(#fields)
270    }
271}
272
273fn init_enum_from_named(name: &Ident, helper_name: &Ident, variant_name: &Ident, fields: &Punctuated<Field, Comma>) -> proc_macro2::TokenStream {
274    let fields = fields.iter().map(|field| {
275        let name = &field.ident;
276        quote! { #name }
277    });
278    
279    let fields_clone = fields.clone();
280
281    quote! {
282        #helper_name::#variant_name { #( #fields ),* } => #name::#variant_name { #( #fields_clone ),* }
283    }
284}
285
286fn init_enum_from_unnamed(name: &Ident, helper_name: &Ident, variant_name: &Ident, fields: &Punctuated<Field, Comma>) -> proc_macro2::TokenStream {
287    let fields = fields.iter().enumerate().map(|(i, _)| {
288        let index = Index::from(i);
289        let name = Ident::new(&format!("value_{}", i), index.span);
290        quote! { #name }
291    });
292    
293    let fields_clone = fields.clone();
294    
295    quote! {
296        #helper_name::#variant_name( #( #fields ),* ) => #name::#variant_name( #( #fields_clone ),* )
297    }
298}
299
300fn init_enum_from_unit(name: &Ident, helper_name: &Ident, variant_name: &Ident) -> proc_macro2::TokenStream {
301    quote! {
302        #helper_name::#variant_name => #name::#variant_name
303    }
304}