struct_validation_derive/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse_macro_input, DeriveInput, Data, Fields, Error};
4
5/// Importing `ValidationError` from the `struct_validation_core` crate.
6/// This is used to annotate validation errors with field-specific information.
7#[allow(unused_imports)]
8use struct_validation_core::ValidationError;
9
10/// Procedural macro to automatically implement the `Validate` trait for structs.
11///
12/// This macro generates an implementation of the `Validate` trait for the annotated struct.
13/// It iterates over each named field in the struct, invokes the `validate` method on each field,
14/// prefixes any resulting `ValidationError` with the field name, and collects all errors into
15/// a single `Vec<ValidationError>`.
16///
17/// # Constraints
18///
19/// - The macro can only be derived for structs with **named fields**.
20/// - Each field in the struct must implement the `Validate` trait.
21///
22/// # Examples
23///
24/// ```rust
25/// use struct_validation_core::{Validate, ValidationError, validate};
26/// use struct_validation_derive::Validate;
27///
28/// struct NonEmptyString(String);
29/// 
30/// impl Validate for NonEmptyString {
31///     fn validate(&self) -> Vec<ValidationError> {
32///         let mut errors = Vec::new();
33///         if self.0.is_empty() {
34///             errors.push(ValidationError::new("String", "must not be empty"));
35///         }
36///         errors
37///     }
38/// }
39/// impl From<String> for NonEmptyString {
40///     fn from(value: String) -> Self {
41///        Self(value)
42///     }
43/// }
44/// 
45/// #[derive(Validate)]
46/// struct User {
47///     username: NonEmptyString,
48///     email: NonEmptyString,
49/// }
50///
51///
52/// fn main() {
53///     let user = User {
54///         username: "".to_string().into(),
55///         email: "invalidemail.com".to_string().into(),
56///     };
57///
58///     let errors = user.validate();
59///
60///     for error in errors {
61///         println!("Error in {}: {}", error.field, error.message);
62///     }
63/// }
64/// ```
65///
66/// **Output:**
67/// ```text
68/// Error in username: must not be empty
69/// Error in email: must not be empty
70/// ```
71#[proc_macro_derive(Validate)]
72pub fn derive_validate(input: TokenStream) -> TokenStream {
73    // Parse the input token stream as a Rust struct
74    let input = parse_macro_input!(input as DeriveInput);
75
76    // Extract the struct name
77    let struct_name = &input.ident;
78
79    // Ensure the input is a struct with named fields
80    let fields = if let Data::Struct(data) = &input.data {
81        match &data.fields {
82            Fields::Named(fields) => &fields.named,
83            _ => {
84                // Emit a compile error if not a struct with named fields
85                return Error::new_spanned(
86                    struct_name,
87                    "Validate can only be derived for structs with named fields",
88                )
89                .to_compile_error()
90                .into();
91            }
92        }
93    } else {
94        // Emit a compile error if not a struct
95        return Error::new_spanned(
96            struct_name,
97            "Validate can only be derived for structs",
98        )
99        .to_compile_error()
100        .into();
101    };
102
103    // Generate validation code for each field, ensuring each implements Validate
104    let validator_iters = fields.iter().map(|field| {
105        // Extract the field name as an identifier
106        let field_name = &field.ident;
107        // Convert the field name to a string for error prefixing
108        let field_name_str = field_name.as_ref().unwrap().to_string();
109
110        quote! {
111            self.#field_name.validate()
112                .into_iter()
113                .map(|mut e| { e.add_prefix(#field_name_str); e })
114        }
115    });
116
117    // Chain all iterators or use an empty iterator if no fields are present
118    let stream = validator_iters.reduce(|acc, stream| {
119        quote! {
120            #acc.chain(#stream)
121        }
122    }).unwrap_or_else(|| quote! { std::iter::empty() });
123
124    // Generate the final implementation of Validate for the struct
125    let expanded = quote! {
126        impl Validate for #struct_name {
127            fn validate(&self) -> Vec<struct_validation::ValidationError> {
128                #stream.collect()
129            }
130        }
131    };
132
133    // Convert the generated code into a TokenStream and return it
134    TokenStream::from(expanded)
135}