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}