domainstack_derive/
lib.rs

1//! # domainstack-derive
2//!
3//! Derive macros for domainstack validation and schema generation.
4//!
5//! ## Provided Macros
6//!
7//! - **`#[derive(Validate)]`** - Automatic validation implementation with `#[validate(...)]` attributes
8//! - **`#[derive(ToSchema)]`** - OpenAPI 3.0 schema generation from validation rules
9//! - **`#[derive(ToJsonSchema)]`** - JSON Schema (Draft 2020-12) generation from validation rules
10//! - **`#[derive(ValidateOnDeserialize)]`** - Validate automatically during serde deserialization (requires `serde` feature)
11//!
12//! ## `#[derive(Validate)]`
13//!
14//! Generates a `Validate` trait implementation that validates all fields with `#[validate(...)]` attributes.
15//!
16//! ### Structs with Named Fields
17//!
18//! ```rust
19//! use domainstack::prelude::*;
20//! use domainstack_derive::Validate;
21//!
22//! #[derive(Debug, Validate)]
23//! struct User {
24//!     #[validate(length(min = 2, max = 50))]
25//!     name: String,
26//!
27//!     #[validate(range(min = 18, max = 120))]
28//!     age: u8,
29//! }
30//!
31//! let user = User { name: "Alice".to_string(), age: 30 };
32//! assert!(user.validate().is_ok());
33//! ```
34//!
35//! ### Tuple Structs (Newtypes)
36//!
37//! Perfect for type-safe wrappers with validation:
38//!
39//! ```rust
40//! use domainstack::prelude::*;
41//! use domainstack_derive::Validate;
42//!
43//! #[derive(Debug, Validate)]
44//! struct Email(#[validate(email)] String);
45//!
46//! #[derive(Debug, Validate)]
47//! struct Age(#[validate(range(min = 0, max = 150))] u8);
48//!
49//! let email = Email("user@example.com".to_string());
50//! assert!(email.validate().is_ok());
51//!
52//! let age = Age(25);
53//! assert!(age.validate().is_ok());
54//! ```
55//!
56//! ### Enums
57//!
58//! Supports unit, tuple, and struct variants:
59//!
60//! ```rust
61//! use domainstack::prelude::*;
62//! use domainstack_derive::Validate;
63//!
64//! #[derive(Debug, Validate)]
65//! enum PaymentMethod {
66//!     // Unit variant - always valid
67//!     Cash,
68//!
69//!     // Tuple variant
70//!     Card(#[validate(length(min = 13, max = 19))] String),
71//!
72//!     // Struct variant
73//!     BankTransfer {
74//!         #[validate(alphanumeric)]
75//!         account: String,
76//!         #[validate(length(min = 6, max = 11))]
77//!         routing: String,
78//!     },
79//! }
80//!
81//! let cash = PaymentMethod::Cash;
82//! assert!(cash.validate().is_ok());
83//!
84//! let card = PaymentMethod::Card("4111111111111111".to_string());
85//! assert!(card.validate().is_ok());
86//! ```
87//!
88//! ## `#[derive(ToSchema)]`
89//!
90//! Generates a `ToSchema` trait implementation that produces OpenAPI 3.0 schemas from validation rules.
91//!
92//! ```rust,ignore
93//! use domainstack_derive::{Validate, ToSchema};
94//!
95//! #[derive(Validate, ToSchema)]
96//! struct User {
97//!     #[validate(email)]
98//!     #[validate(max_len = 255)]
99//!     email: String,
100//!
101//!     #[validate(range(min = 18, max = 120))]
102//!     age: u8,
103//! }
104//!
105//! let schema = User::schema();
106//! // → email: { type: "string", format: "email", maxLength: 255 }
107//! // → age: { type: "integer", minimum: 18, maximum: 120 }
108//! ```
109//!
110//! ## `#[derive(ToJsonSchema)]`
111//!
112//! Generates a `ToJsonSchema` trait implementation that produces JSON Schema (Draft 2020-12) from validation rules.
113//! Requires the `schema` feature.
114//!
115//! ```rust,ignore
116//! use domainstack_derive::{Validate, ToJsonSchema};
117//!
118//! #[derive(Validate, ToJsonSchema)]
119//! struct User {
120//!     #[validate(email)]
121//!     #[validate(max_len = 255)]
122//!     email: String,
123//!
124//!     #[validate(range(min = 18, max = 120))]
125//!     age: u8,
126//! }
127//!
128//! let schema = User::json_schema();
129//! // → email: { type: "string", format: "email", maxLength: 255 }
130//! // → age: { type: "integer", minimum: 18, maximum: 120 }
131//! ```
132//!
133//! ## `#[derive(ValidateOnDeserialize)]`
134//!
135//! Validates during serde deserialization, returning validation errors instead of serde errors.
136//!
137//! ```rust,ignore
138//! use domainstack_derive::ValidateOnDeserialize;
139//!
140//! #[derive(ValidateOnDeserialize)]
141//! struct User {
142//!     #[validate(email)]
143//!     email: String,
144//!
145//!     #[validate(range(min = 18, max = 120))]
146//!     age: u8,
147//! }
148//!
149//! // Validation happens automatically during deserialization
150//! let user: User = serde_json::from_str(r#"{"email": "alice@example.com", "age": 30}"#)?;
151//! // ↑ If this succeeds, user is guaranteed valid!
152//! ```
153
154use proc_macro::TokenStream;
155use quote::quote;
156use syn::{parse_macro_input, Attribute, Data, DeriveInput, Expr, Field, Fields, Lit, Meta};
157
158#[cfg(feature = "schema")]
159mod json_schema;
160mod schema;
161
162#[proc_macro_derive(Validate, attributes(validate))]
163pub fn derive_validate(input: TokenStream) -> TokenStream {
164    let input = parse_macro_input!(input as DeriveInput);
165
166    match generate_validate_impl(&input) {
167        Ok(tokens) => tokens.into(),
168        Err(err) => err.to_compile_error().into(),
169    }
170}
171
172#[proc_macro_derive(ToSchema, attributes(schema, validate))]
173pub fn derive_to_schema(input: TokenStream) -> TokenStream {
174    let input = parse_macro_input!(input as DeriveInput);
175
176    match schema::derive_to_schema_impl(input) {
177        Ok(tokens) => tokens.into(),
178        Err(err) => err.to_compile_error().into(),
179    }
180}
181
182#[cfg(feature = "schema")]
183#[proc_macro_derive(ToJsonSchema, attributes(schema, validate))]
184pub fn derive_to_json_schema(input: TokenStream) -> TokenStream {
185    let input = parse_macro_input!(input as DeriveInput);
186
187    match json_schema::derive_to_json_schema_impl(input) {
188        Ok(tokens) => tokens.into(),
189        Err(err) => err.to_compile_error().into(),
190    }
191}
192
193#[cfg(feature = "serde")]
194#[proc_macro_derive(ValidateOnDeserialize, attributes(validate, serde))]
195pub fn derive_validate_on_deserialize(input: TokenStream) -> TokenStream {
196    let input = parse_macro_input!(input as DeriveInput);
197
198    match generate_validate_on_deserialize_impl(&input) {
199        Ok(tokens) => tokens.into(),
200        Err(err) => err.to_compile_error().into(),
201    }
202}
203
204#[cfg(feature = "serde")]
205fn generate_validate_on_deserialize_impl(
206    input: &DeriveInput,
207) -> syn::Result<proc_macro2::TokenStream> {
208    let name = &input.ident;
209    let generics = &input.generics;
210    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
211
212    // Only support structs with named fields
213    let fields = match &input.data {
214        Data::Struct(data) => match &data.fields {
215            Fields::Named(fields) => &fields.named,
216            _ => {
217                return Err(syn::Error::new_spanned(
218                    input,
219                    "#[derive(ValidateOnDeserialize)] only supports structs with named fields",
220                ))
221            }
222        },
223        _ => {
224            return Err(syn::Error::new_spanned(
225                input,
226                "#[derive(ValidateOnDeserialize)] only supports structs",
227            ))
228        }
229    };
230
231    // Parse struct-level validation checks (reuse existing function)
232    let struct_validations = parse_struct_attributes(input)?;
233
234    // Parse validation rules for each field (reuse existing function)
235    let mut field_validations = Vec::new();
236    for field in fields {
237        let field_name = field.ident.as_ref().unwrap().clone();
238        let field_type = field.ty.clone();
239        let rules = parse_field_attributes(field)?;
240
241        if !rules.is_empty() {
242            field_validations.push(FieldValidation {
243                field_name,
244                field_type,
245                rules,
246            });
247        }
248    }
249
250    // Generate validation code for each field (reuse existing function)
251    let field_validation_code = field_validations.iter().map(generate_field_validation);
252
253    // Generate validation code for struct-level checks (reuse existing function)
254    let struct_validation_code = struct_validations.iter().map(generate_struct_validation);
255
256    // Generate intermediate struct name
257    let intermediate_name = syn::Ident::new(&format!("{}Intermediate", name), name.span());
258
259    // Extract field names and types
260    let field_names: Vec<_> = fields.iter().map(|f| f.ident.as_ref().unwrap()).collect();
261
262    let field_types: Vec<_> = fields.iter().map(|f| &f.ty).collect();
263
264    // Forward serde attributes from the original struct to the intermediate struct
265    // This ensures rename, rename_all, etc. work correctly
266    let struct_serde_attrs: Vec<_> = input
267        .attrs
268        .iter()
269        .filter(|attr| attr.path().is_ident("serde"))
270        .collect();
271
272    // Forward serde attributes for each field
273    let field_serde_attrs: Vec<Vec<_>> = fields
274        .iter()
275        .map(|f| {
276            f.attrs
277                .iter()
278                .filter(|attr| attr.path().is_ident("serde"))
279                .collect()
280        })
281        .collect();
282
283    // Generate the expanded code
284    let expanded = quote! {
285        // Intermediate struct for deserialization
286        #[derive(::serde::Deserialize)]
287        #[doc(hidden)]
288        #( #struct_serde_attrs )*
289        struct #intermediate_name #generics {
290            #(
291                #( #field_serde_attrs )*
292                #field_names: #field_types,
293            )*
294        }
295
296        // Implement Deserialize for the main struct
297        impl<'de> ::serde::Deserialize<'de> for #name #ty_generics #where_clause {
298            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
299            where
300                D: ::serde::Deserializer<'de>,
301            {
302                // Phase 1: Deserialize into intermediate struct
303                let intermediate = #intermediate_name::deserialize(deserializer)?;
304
305                // Phase 2: Construct the final struct
306                let value = #name {
307                    #( #field_names: intermediate.#field_names, )*
308                };
309
310                // Phase 3: Validate using fully qualified syntax
311                <#name #ty_generics as ::domainstack::Validate>::validate(&value)
312                    .map_err(|e| ::serde::de::Error::custom(format!("Validation failed: {}", e)))?;
313
314                Ok(value)
315            }
316        }
317
318        // Generate Validate implementation with actual validation logic
319        impl #impl_generics ::domainstack::Validate for #name #ty_generics #where_clause {
320            fn validate(&self) -> Result<(), ::domainstack::ValidationError> {
321                let mut err = ::domainstack::ValidationError::default();
322
323                // Field-level validations
324                #(#field_validation_code)*
325
326                // Struct-level validations (cross-field checks)
327                #(#struct_validation_code)*
328
329                if err.is_empty() { Ok(()) } else { Err(err) }
330            }
331        }
332    };
333
334    Ok(expanded)
335}
336
337#[derive(Debug, Clone)]
338#[allow(dead_code)]
339enum ValidationRule {
340    // Existing rules
341    Length {
342        min: Option<usize>,
343        max: Option<usize>,
344        code: Option<String>,
345        message: Option<String>,
346    },
347    Range {
348        min: Option<proc_macro2::TokenStream>,
349        max: Option<proc_macro2::TokenStream>,
350        code: Option<String>,
351        message: Option<String>,
352    },
353    Nested,
354    Each(Box<ValidationRule>),
355    Custom(String),
356
357    // New rich syntax rules
358    Email,
359    Url,
360    MinLen(usize),
361    MaxLen(usize),
362    Alphanumeric,
363    Ascii,
364    AlphaOnly,
365    NumericString,
366    NonEmpty,
367    NonBlank,
368    NoWhitespace,
369    Contains(String),
370    StartsWith(String),
371    EndsWith(String),
372    MatchesRegex(String),
373    Min(proc_macro2::TokenStream),
374    Max(proc_macro2::TokenStream),
375    Positive,
376    Negative,
377    NonZero,
378    Finite,
379    MultipleOf(proc_macro2::TokenStream),
380    Equals(proc_macro2::TokenStream),
381    NotEquals(proc_macro2::TokenStream),
382    OneOf(Vec<String>),
383    MinItems(usize),
384    MaxItems(usize),
385    Unique,
386}
387
388#[derive(Debug, Clone)]
389struct StructValidation {
390    check: String,
391    code: Option<String>,
392    message: Option<String>,
393    when: Option<String>,
394}
395
396#[derive(Debug)]
397#[allow(dead_code)]
398struct FieldValidation {
399    field_name: syn::Ident,
400    field_type: syn::Type,
401    rules: Vec<ValidationRule>,
402}
403
404fn generate_validate_impl(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
405    let name = &input.ident;
406    let generics = &input.generics;
407    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
408
409    match &input.data {
410        Data::Struct(data) => match &data.fields {
411            Fields::Named(fields) => generate_named_struct_validate(
412                name,
413                &impl_generics,
414                &ty_generics,
415                where_clause,
416                &fields.named,
417                input,
418            ),
419            Fields::Unnamed(fields) => generate_tuple_struct_validate(
420                name,
421                &impl_generics,
422                &ty_generics,
423                where_clause,
424                &fields.unnamed,
425                input,
426            ),
427            Fields::Unit => {
428                // Unit structs always pass validation
429                Ok(quote! {
430                    impl #impl_generics domainstack::Validate for #name #ty_generics #where_clause {
431                        fn validate(&self) -> Result<(), domainstack::ValidationError> {
432                            Ok(())
433                        }
434                    }
435                })
436            }
437        },
438        Data::Enum(data) => generate_enum_validate(
439            name,
440            &impl_generics,
441            &ty_generics,
442            where_clause,
443            &data.variants,
444            input,
445        ),
446        Data::Union(_) => Err(syn::Error::new_spanned(
447            input,
448            "#[derive(Validate)] does not support unions",
449        )),
450    }
451}
452
453/// Generate Validate impl for structs with named fields
454fn generate_named_struct_validate(
455    name: &syn::Ident,
456    impl_generics: &syn::ImplGenerics,
457    ty_generics: &syn::TypeGenerics,
458    where_clause: Option<&syn::WhereClause>,
459    fields: &syn::punctuated::Punctuated<Field, syn::Token![,]>,
460    input: &DeriveInput,
461) -> syn::Result<proc_macro2::TokenStream> {
462    // Parse struct-level validation checks
463    let struct_validations = parse_struct_attributes(input)?;
464
465    // Parse validation rules for each field
466    let mut field_validations = Vec::new();
467    for field in fields {
468        let field_name = field.ident.as_ref().unwrap().clone();
469        let field_type = field.ty.clone();
470        let rules = parse_field_attributes(field)?;
471
472        if !rules.is_empty() {
473            field_validations.push(FieldValidation {
474                field_name,
475                field_type,
476                rules,
477            });
478        }
479    }
480
481    // Generate validation code for each field
482    let field_validation_code = field_validations.iter().map(generate_field_validation);
483
484    // Generate validation code for struct-level checks
485    let struct_validation_code = struct_validations.iter().map(generate_struct_validation);
486
487    let expanded = quote! {
488        impl #impl_generics domainstack::Validate for #name #ty_generics #where_clause {
489            fn validate(&self) -> Result<(), domainstack::ValidationError> {
490                let mut err = domainstack::ValidationError::default();
491
492                // Field-level validations
493                #(#field_validation_code)*
494
495                // Struct-level validations (cross-field checks)
496                #(#struct_validation_code)*
497
498                if err.is_empty() { Ok(()) } else { Err(err) }
499            }
500        }
501    };
502
503    Ok(expanded)
504}
505
506/// Validation info for tuple struct fields (indexed)
507#[derive(Debug)]
508#[allow(dead_code)]
509struct TupleFieldValidation {
510    field_index: usize,
511    field_type: syn::Type,
512    rules: Vec<ValidationRule>,
513}
514
515/// Generate Validate impl for tuple structs (newtypes)
516fn generate_tuple_struct_validate(
517    name: &syn::Ident,
518    impl_generics: &syn::ImplGenerics,
519    ty_generics: &syn::TypeGenerics,
520    where_clause: Option<&syn::WhereClause>,
521    fields: &syn::punctuated::Punctuated<Field, syn::Token![,]>,
522    input: &DeriveInput,
523) -> syn::Result<proc_macro2::TokenStream> {
524    // Parse struct-level validation checks
525    let struct_validations = parse_struct_attributes(input)?;
526
527    // Parse validation rules for each field by index
528    let mut field_validations = Vec::new();
529    for (index, field) in fields.iter().enumerate() {
530        let field_type = field.ty.clone();
531        let rules = parse_field_attributes(field)?;
532
533        if !rules.is_empty() {
534            field_validations.push(TupleFieldValidation {
535                field_index: index,
536                field_type,
537                rules,
538            });
539        }
540    }
541
542    // Generate validation code for each field
543    let field_validation_code = field_validations
544        .iter()
545        .map(generate_tuple_field_validation);
546
547    // Generate validation code for struct-level checks
548    let struct_validation_code = struct_validations.iter().map(generate_struct_validation);
549
550    let expanded = quote! {
551        impl #impl_generics domainstack::Validate for #name #ty_generics #where_clause {
552            fn validate(&self) -> Result<(), domainstack::ValidationError> {
553                let mut err = domainstack::ValidationError::default();
554
555                // Field-level validations
556                #(#field_validation_code)*
557
558                // Struct-level validations (cross-field checks)
559                #(#struct_validation_code)*
560
561                if err.is_empty() { Ok(()) } else { Err(err) }
562            }
563        }
564    };
565
566    Ok(expanded)
567}
568
569/// Generate Validate impl for enums
570fn generate_enum_validate(
571    name: &syn::Ident,
572    impl_generics: &syn::ImplGenerics,
573    ty_generics: &syn::TypeGenerics,
574    where_clause: Option<&syn::WhereClause>,
575    variants: &syn::punctuated::Punctuated<syn::Variant, syn::Token![,]>,
576    _input: &DeriveInput,
577) -> syn::Result<proc_macro2::TokenStream> {
578    let mut variant_arms = Vec::new();
579
580    for variant in variants {
581        let variant_name = &variant.ident;
582
583        match &variant.fields {
584            Fields::Named(fields) => {
585                // Struct variant: EnumName::Variant { field1, field2, ... }
586                let field_names: Vec<_> = fields
587                    .named
588                    .iter()
589                    .map(|f| f.ident.as_ref().unwrap())
590                    .collect();
591
592                // Parse validation rules for each field
593                let mut validations = Vec::new();
594                for field in &fields.named {
595                    let field_name = field.ident.as_ref().unwrap();
596                    let field_name_str = field_name.to_string();
597                    let rules = parse_field_attributes(field)?;
598
599                    for rule in rules {
600                        let validation_code =
601                            generate_enum_field_validation(field_name, &field_name_str, &rule);
602                        validations.push(validation_code);
603                    }
604                }
605
606                if validations.is_empty() {
607                    variant_arms.push(quote! {
608                        #name::#variant_name { .. } => {}
609                    });
610                } else {
611                    variant_arms.push(quote! {
612                        #name::#variant_name { #(#field_names),* } => {
613                            #(#validations)*
614                        }
615                    });
616                }
617            }
618            Fields::Unnamed(fields) => {
619                // Tuple variant: EnumName::Variant(field0, field1, ...)
620                let field_bindings: Vec<_> = (0..fields.unnamed.len())
621                    .map(|i| {
622                        syn::Ident::new(&format!("field_{}", i), proc_macro2::Span::call_site())
623                    })
624                    .collect();
625
626                // Parse validation rules for each field
627                let mut validations = Vec::new();
628                for (index, field) in fields.unnamed.iter().enumerate() {
629                    let binding = &field_bindings[index];
630                    let field_name_str = index.to_string();
631                    let rules = parse_field_attributes(field)?;
632
633                    for rule in rules {
634                        let validation_code =
635                            generate_enum_tuple_field_validation(binding, &field_name_str, &rule);
636                        validations.push(validation_code);
637                    }
638                }
639
640                if validations.is_empty() {
641                    variant_arms.push(quote! {
642                        #name::#variant_name(..) => {}
643                    });
644                } else {
645                    variant_arms.push(quote! {
646                        #name::#variant_name(#(#field_bindings),*) => {
647                            #(#validations)*
648                        }
649                    });
650                }
651            }
652            Fields::Unit => {
653                // Unit variant: EnumName::Variant
654                variant_arms.push(quote! {
655                    #name::#variant_name => {}
656                });
657            }
658        }
659    }
660
661    let expanded = quote! {
662        impl #impl_generics domainstack::Validate for #name #ty_generics #where_clause {
663            fn validate(&self) -> Result<(), domainstack::ValidationError> {
664                let mut err = domainstack::ValidationError::default();
665
666                match self {
667                    #(#variant_arms)*
668                }
669
670                if err.is_empty() { Ok(()) } else { Err(err) }
671            }
672        }
673    };
674
675    Ok(expanded)
676}
677
678fn parse_struct_attributes(input: &DeriveInput) -> syn::Result<Vec<StructValidation>> {
679    let mut validations = Vec::new();
680
681    for attr in &input.attrs {
682        if !attr.path().is_ident("validate") {
683            continue;
684        }
685
686        let validation = parse_struct_validate_attribute(attr)?;
687        validations.push(validation);
688    }
689
690    Ok(validations)
691}
692
693fn parse_struct_validate_attribute(attr: &Attribute) -> syn::Result<StructValidation> {
694    let meta = &attr.meta;
695
696    match meta {
697        Meta::List(list) => {
698            let nested: syn::punctuated::Punctuated<Meta, syn::Token![,]> =
699                list.parse_args_with(syn::punctuated::Punctuated::parse_terminated)?;
700
701            let mut check = None;
702            let mut code = None;
703            let mut message = None;
704            let mut when = None;
705
706            for meta in nested {
707                match meta {
708                    Meta::NameValue(nv) => {
709                        if nv.path.is_ident("check") {
710                            check = Some(parse_string_lit(&nv.value)?);
711                        } else if nv.path.is_ident("code") {
712                            code = Some(parse_string_lit(&nv.value)?);
713                        } else if nv.path.is_ident("message") {
714                            message = Some(parse_string_lit(&nv.value)?);
715                        } else if nv.path.is_ident("when") {
716                            when = Some(parse_string_lit(&nv.value)?);
717                        }
718                    }
719                    _ => return Err(syn::Error::new_spanned(meta, "Expected name = value")),
720                }
721            }
722
723            let check = check.ok_or_else(|| {
724                syn::Error::new_spanned(attr, "Struct-level validation requires 'check' parameter")
725            })?;
726
727            Ok(StructValidation {
728                check,
729                code,
730                message,
731                when,
732            })
733        }
734        _ => Err(syn::Error::new_spanned(
735            attr,
736            "Struct-level validation requires #[validate(check = \"...\", ...)]",
737        )),
738    }
739}
740
741fn parse_field_attributes(field: &Field) -> syn::Result<Vec<ValidationRule>> {
742    let mut rules = Vec::new();
743
744    for attr in &field.attrs {
745        if !attr.path().is_ident("validate") {
746            continue;
747        }
748
749        // Parse all rules from this attribute
750        attr.parse_nested_meta(|meta| {
751            // Email
752            if meta.path.is_ident("email") {
753                rules.push(ValidationRule::Email);
754                return Ok(());
755            }
756
757            // URL
758            if meta.path.is_ident("url") {
759                rules.push(ValidationRule::Url);
760                return Ok(());
761            }
762
763            // min_len
764            if meta.path.is_ident("min_len") {
765                let value: syn::Lit = meta.value()?.parse()?;
766                if let syn::Lit::Int(lit_int) = value {
767                    let val = lit_int.base10_parse()?;
768                    rules.push(ValidationRule::MinLen(val));
769                }
770                return Ok(());
771            }
772
773            // max_len
774            if meta.path.is_ident("max_len") {
775                let value: syn::Lit = meta.value()?.parse()?;
776                if let syn::Lit::Int(lit_int) = value {
777                    let val = lit_int.base10_parse()?;
778                    rules.push(ValidationRule::MaxLen(val));
779                }
780                return Ok(());
781            }
782
783            // length (legacy syntax)
784            if meta.path.is_ident("length") {
785                let mut min = None;
786                let mut max = None;
787                let mut code = None;
788                let mut message = None;
789
790                meta.parse_nested_meta(|nested| {
791                    if nested.path.is_ident("min") {
792                        let value: syn::Lit = nested.value()?.parse()?;
793                        if let syn::Lit::Int(lit_int) = value {
794                            min = Some(lit_int.base10_parse()?);
795                        }
796                    } else if nested.path.is_ident("max") {
797                        let value: syn::Lit = nested.value()?.parse()?;
798                        if let syn::Lit::Int(lit_int) = value {
799                            max = Some(lit_int.base10_parse()?);
800                        }
801                    } else if nested.path.is_ident("code") {
802                        let value: syn::Lit = nested.value()?.parse()?;
803                        if let syn::Lit::Str(lit_str) = value {
804                            code = Some(lit_str.value());
805                        }
806                    } else if nested.path.is_ident("message") {
807                        let value: syn::Lit = nested.value()?.parse()?;
808                        if let syn::Lit::Str(lit_str) = value {
809                            message = Some(lit_str.value());
810                        }
811                    }
812                    Ok(())
813                })?;
814
815                rules.push(ValidationRule::Length {
816                    min,
817                    max,
818                    code,
819                    message,
820                });
821                return Ok(());
822            }
823
824            // range
825            if meta.path.is_ident("range") {
826                let mut min = None;
827                let mut max = None;
828                let mut code = None;
829                let mut message = None;
830
831                meta.parse_nested_meta(|nested| {
832                    if nested.path.is_ident("min") {
833                        let value: syn::Expr = nested.value()?.parse()?;
834                        min = Some(quote! { #value });
835                    } else if nested.path.is_ident("max") {
836                        let value: syn::Expr = nested.value()?.parse()?;
837                        max = Some(quote! { #value });
838                    } else if nested.path.is_ident("code") {
839                        let value: syn::Lit = nested.value()?.parse()?;
840                        if let syn::Lit::Str(lit_str) = value {
841                            code = Some(lit_str.value());
842                        }
843                    } else if nested.path.is_ident("message") {
844                        let value: syn::Lit = nested.value()?.parse()?;
845                        if let syn::Lit::Str(lit_str) = value {
846                            message = Some(lit_str.value());
847                        }
848                    }
849                    Ok(())
850                })?;
851
852                rules.push(ValidationRule::Range {
853                    min,
854                    max,
855                    code,
856                    message,
857                });
858                return Ok(());
859            }
860
861            // nested
862            if meta.path.is_ident("nested") {
863                rules.push(ValidationRule::Nested);
864                return Ok(());
865            }
866
867            // each - supports any validation rule
868            if meta.path.is_ident("each") {
869                meta.parse_nested_meta(|nested| {
870                    // Handle nested
871                    if nested.path.is_ident("nested") {
872                        rules.push(ValidationRule::Each(Box::new(ValidationRule::Nested)));
873                        return Ok(());
874                    }
875
876                    // Handle length
877                    if nested.path.is_ident("length") {
878                        let mut min = None;
879                        let mut max = None;
880                        nested.parse_nested_meta(|inner| {
881                            if inner.path.is_ident("min") {
882                                let value: syn::Lit = inner.value()?.parse()?;
883                                if let syn::Lit::Int(lit_int) = value {
884                                    min = Some(lit_int.base10_parse()?);
885                                }
886                            } else if inner.path.is_ident("max") {
887                                let value: syn::Lit = inner.value()?.parse()?;
888                                if let syn::Lit::Int(lit_int) = value {
889                                    max = Some(lit_int.base10_parse()?);
890                                }
891                            }
892                            Ok(())
893                        })?;
894                        rules.push(ValidationRule::Each(Box::new(ValidationRule::Length {
895                            min,
896                            max,
897                            code: None,
898                            message: None,
899                        })));
900                        return Ok(());
901                    }
902
903                    // Handle range
904                    if nested.path.is_ident("range") {
905                        let mut min = None;
906                        let mut max = None;
907                        nested.parse_nested_meta(|inner| {
908                            if inner.path.is_ident("min") {
909                                let value: syn::Expr = inner.value()?.parse()?;
910                                min = Some(quote! { #value });
911                            } else if inner.path.is_ident("max") {
912                                let value: syn::Expr = inner.value()?.parse()?;
913                                max = Some(quote! { #value });
914                            }
915                            Ok(())
916                        })?;
917                        rules.push(ValidationRule::Each(Box::new(ValidationRule::Range {
918                            min,
919                            max,
920                            code: None,
921                            message: None,
922                        })));
923                        return Ok(());
924                    }
925
926                    // Handle all simple string rules
927                    if nested.path.is_ident("email") {
928                        rules.push(ValidationRule::Each(Box::new(ValidationRule::Email)));
929                        return Ok(());
930                    }
931                    if nested.path.is_ident("url") {
932                        rules.push(ValidationRule::Each(Box::new(ValidationRule::Url)));
933                        return Ok(());
934                    }
935                    if nested.path.is_ident("alphanumeric") {
936                        rules.push(ValidationRule::Each(Box::new(ValidationRule::Alphanumeric)));
937                        return Ok(());
938                    }
939                    if nested.path.is_ident("ascii") {
940                        rules.push(ValidationRule::Each(Box::new(ValidationRule::Ascii)));
941                        return Ok(());
942                    }
943                    if nested.path.is_ident("alpha_only") {
944                        rules.push(ValidationRule::Each(Box::new(ValidationRule::AlphaOnly)));
945                        return Ok(());
946                    }
947                    if nested.path.is_ident("numeric_string") {
948                        rules.push(ValidationRule::Each(Box::new(
949                            ValidationRule::NumericString,
950                        )));
951                        return Ok(());
952                    }
953                    if nested.path.is_ident("non_empty") {
954                        rules.push(ValidationRule::Each(Box::new(ValidationRule::NonEmpty)));
955                        return Ok(());
956                    }
957                    if nested.path.is_ident("non_blank") {
958                        rules.push(ValidationRule::Each(Box::new(ValidationRule::NonBlank)));
959                        return Ok(());
960                    }
961
962                    // Handle rules with parameters
963                    if nested.path.is_ident("min_len") {
964                        let value: syn::Lit = nested.value()?.parse()?;
965                        if let syn::Lit::Int(lit_int) = value {
966                            let val = lit_int.base10_parse()?;
967                            rules.push(ValidationRule::Each(Box::new(ValidationRule::MinLen(val))));
968                        }
969                        return Ok(());
970                    }
971                    if nested.path.is_ident("max_len") {
972                        let value: syn::Lit = nested.value()?.parse()?;
973                        if let syn::Lit::Int(lit_int) = value {
974                            let val = lit_int.base10_parse()?;
975                            rules.push(ValidationRule::Each(Box::new(ValidationRule::MaxLen(val))));
976                        }
977                        return Ok(());
978                    }
979                    if nested.path.is_ident("matches_regex") {
980                        let value: syn::Lit = nested.value()?.parse()?;
981                        if let syn::Lit::Str(lit_str) = value {
982                            rules.push(ValidationRule::Each(Box::new(
983                                ValidationRule::MatchesRegex(lit_str.value()),
984                            )));
985                        }
986                        return Ok(());
987                    }
988
989                    Ok(())
990                })?;
991                return Ok(());
992            }
993
994            // custom
995            if meta.path.is_ident("custom") {
996                let value: syn::Lit = meta.value()?.parse()?;
997                if let syn::Lit::Str(lit_str) = value {
998                    rules.push(ValidationRule::Custom(lit_str.value()));
999                }
1000                return Ok(());
1001            }
1002
1003            // String pattern rules
1004            if meta.path.is_ident("alphanumeric") {
1005                rules.push(ValidationRule::Alphanumeric);
1006                return Ok(());
1007            }
1008
1009            if meta.path.is_ident("ascii") {
1010                rules.push(ValidationRule::Ascii);
1011                return Ok(());
1012            }
1013
1014            if meta.path.is_ident("alpha_only") {
1015                rules.push(ValidationRule::AlphaOnly);
1016                return Ok(());
1017            }
1018
1019            if meta.path.is_ident("numeric_string") {
1020                rules.push(ValidationRule::NumericString);
1021                return Ok(());
1022            }
1023
1024            if meta.path.is_ident("non_empty") {
1025                rules.push(ValidationRule::NonEmpty);
1026                return Ok(());
1027            }
1028
1029            if meta.path.is_ident("non_blank") {
1030                rules.push(ValidationRule::NonBlank);
1031                return Ok(());
1032            }
1033
1034            if meta.path.is_ident("no_whitespace") {
1035                rules.push(ValidationRule::NoWhitespace);
1036                return Ok(());
1037            }
1038
1039            // String content rules with values
1040            if meta.path.is_ident("contains") {
1041                let value: syn::Lit = meta.value()?.parse()?;
1042                if let syn::Lit::Str(lit_str) = value {
1043                    rules.push(ValidationRule::Contains(lit_str.value()));
1044                }
1045                return Ok(());
1046            }
1047
1048            if meta.path.is_ident("starts_with") {
1049                let value: syn::Lit = meta.value()?.parse()?;
1050                if let syn::Lit::Str(lit_str) = value {
1051                    rules.push(ValidationRule::StartsWith(lit_str.value()));
1052                }
1053                return Ok(());
1054            }
1055
1056            if meta.path.is_ident("ends_with") {
1057                let value: syn::Lit = meta.value()?.parse()?;
1058                if let syn::Lit::Str(lit_str) = value {
1059                    rules.push(ValidationRule::EndsWith(lit_str.value()));
1060                }
1061                return Ok(());
1062            }
1063
1064            if meta.path.is_ident("matches_regex") {
1065                let value: syn::Lit = meta.value()?.parse()?;
1066                if let syn::Lit::Str(lit_str) = value {
1067                    rules.push(ValidationRule::MatchesRegex(lit_str.value()));
1068                }
1069                return Ok(());
1070            }
1071
1072            // Numeric rules
1073            if meta.path.is_ident("min") {
1074                let value: syn::Expr = meta.value()?.parse()?;
1075                rules.push(ValidationRule::Min(quote! { #value }));
1076                return Ok(());
1077            }
1078
1079            if meta.path.is_ident("max") {
1080                let value: syn::Expr = meta.value()?.parse()?;
1081                rules.push(ValidationRule::Max(quote! { #value }));
1082                return Ok(());
1083            }
1084
1085            if meta.path.is_ident("positive") {
1086                rules.push(ValidationRule::Positive);
1087                return Ok(());
1088            }
1089
1090            if meta.path.is_ident("negative") {
1091                rules.push(ValidationRule::Negative);
1092                return Ok(());
1093            }
1094
1095            if meta.path.is_ident("non_zero") {
1096                rules.push(ValidationRule::NonZero);
1097                return Ok(());
1098            }
1099
1100            if meta.path.is_ident("finite") {
1101                rules.push(ValidationRule::Finite);
1102                return Ok(());
1103            }
1104
1105            if meta.path.is_ident("multiple_of") {
1106                let value: syn::Expr = meta.value()?.parse()?;
1107                rules.push(ValidationRule::MultipleOf(quote! { #value }));
1108                return Ok(());
1109            }
1110
1111            // Choice rules
1112            if meta.path.is_ident("equals") {
1113                let value: syn::Expr = meta.value()?.parse()?;
1114                rules.push(ValidationRule::Equals(quote! { #value }));
1115                return Ok(());
1116            }
1117
1118            if meta.path.is_ident("not_equals") {
1119                let value: syn::Expr = meta.value()?.parse()?;
1120                rules.push(ValidationRule::NotEquals(quote! { #value }));
1121                return Ok(());
1122            }
1123
1124            // Collection rules
1125            if meta.path.is_ident("min_items") {
1126                let value: syn::Lit = meta.value()?.parse()?;
1127                if let syn::Lit::Int(lit_int) = value {
1128                    let val = lit_int.base10_parse()?;
1129                    rules.push(ValidationRule::MinItems(val));
1130                }
1131                return Ok(());
1132            }
1133
1134            if meta.path.is_ident("max_items") {
1135                let value: syn::Lit = meta.value()?.parse()?;
1136                if let syn::Lit::Int(lit_int) = value {
1137                    let val = lit_int.base10_parse()?;
1138                    rules.push(ValidationRule::MaxItems(val));
1139                }
1140                return Ok(());
1141            }
1142
1143            if meta.path.is_ident("unique") {
1144                rules.push(ValidationRule::Unique);
1145                return Ok(());
1146            }
1147
1148            // Unknown rule - silently ignore for forward compatibility
1149            Ok(())
1150        })?;
1151    }
1152
1153    Ok(rules)
1154}
1155
1156fn parse_string_lit(expr: &Expr) -> syn::Result<String> {
1157    match expr {
1158        Expr::Lit(lit_expr) => match &lit_expr.lit {
1159            Lit::Str(str_lit) => Ok(str_lit.value()),
1160            _ => Err(syn::Error::new_spanned(expr, "Expected string literal")),
1161        },
1162        _ => Err(syn::Error::new_spanned(expr, "Expected string literal")),
1163    }
1164}
1165
1166fn generate_field_validation(fv: &FieldValidation) -> proc_macro2::TokenStream {
1167    let field_name = &fv.field_name;
1168    let field_name_str = field_name.to_string();
1169
1170    let validations: Vec<_> = fv
1171        .rules
1172        .iter()
1173        .map(|rule| match rule {
1174            // Legacy rules
1175            ValidationRule::Length { min, max, .. } => {
1176                generate_length_validation(field_name, &field_name_str, min, max)
1177            }
1178            ValidationRule::Range { min, max, .. } => {
1179                generate_range_validation(field_name, &field_name_str, min, max)
1180            }
1181            ValidationRule::Nested => generate_nested_validation(field_name, &field_name_str),
1182            ValidationRule::Each(inner_rule) => {
1183                generate_each_validation(field_name, &field_name_str, inner_rule)
1184            }
1185            ValidationRule::Custom(fn_path) => {
1186                generate_custom_validation(field_name, &field_name_str, fn_path)
1187            }
1188
1189            // New rich syntax rules - String rules
1190            ValidationRule::Email => {
1191                generate_simple_string_rule(field_name, &field_name_str, "email")
1192            }
1193            ValidationRule::Url => generate_simple_string_rule(field_name, &field_name_str, "url"),
1194            ValidationRule::MinLen(min) => generate_min_len(field_name, &field_name_str, *min),
1195            ValidationRule::MaxLen(max) => generate_max_len(field_name, &field_name_str, *max),
1196            ValidationRule::Alphanumeric => {
1197                generate_simple_string_rule(field_name, &field_name_str, "alphanumeric")
1198            }
1199            ValidationRule::Ascii => {
1200                generate_simple_string_rule(field_name, &field_name_str, "ascii")
1201            }
1202            ValidationRule::AlphaOnly => {
1203                generate_simple_string_rule(field_name, &field_name_str, "alpha_only")
1204            }
1205            ValidationRule::NumericString => {
1206                generate_simple_string_rule(field_name, &field_name_str, "numeric_string")
1207            }
1208            ValidationRule::NonEmpty => {
1209                generate_simple_string_rule(field_name, &field_name_str, "non_empty")
1210            }
1211            ValidationRule::NonBlank => {
1212                generate_simple_string_rule(field_name, &field_name_str, "non_blank")
1213            }
1214            ValidationRule::NoWhitespace => {
1215                generate_simple_string_rule(field_name, &field_name_str, "no_whitespace")
1216            }
1217            ValidationRule::Contains(substr) => {
1218                generate_string_param_rule(field_name, &field_name_str, "contains", substr)
1219            }
1220            ValidationRule::StartsWith(prefix) => {
1221                generate_string_param_rule(field_name, &field_name_str, "starts_with", prefix)
1222            }
1223            ValidationRule::EndsWith(suffix) => {
1224                generate_string_param_rule(field_name, &field_name_str, "ends_with", suffix)
1225            }
1226            ValidationRule::MatchesRegex(pattern) => {
1227                generate_matches_regex(field_name, &field_name_str, pattern)
1228            }
1229
1230            // Numeric rules
1231            ValidationRule::Min(min) => generate_min_max(field_name, &field_name_str, "min", min),
1232            ValidationRule::Max(max) => generate_min_max(field_name, &field_name_str, "max", max),
1233            ValidationRule::Positive => {
1234                generate_simple_numeric_rule(field_name, &field_name_str, "positive")
1235            }
1236            ValidationRule::Negative => {
1237                generate_simple_numeric_rule(field_name, &field_name_str, "negative")
1238            }
1239            ValidationRule::NonZero => {
1240                generate_simple_numeric_rule(field_name, &field_name_str, "non_zero")
1241            }
1242            ValidationRule::Finite => {
1243                generate_simple_numeric_rule(field_name, &field_name_str, "finite")
1244            }
1245            ValidationRule::MultipleOf(n) => {
1246                generate_min_max(field_name, &field_name_str, "multiple_of", n)
1247            }
1248
1249            // Choice rules
1250            ValidationRule::Equals(val) => {
1251                generate_min_max(field_name, &field_name_str, "equals", val)
1252            }
1253            ValidationRule::NotEquals(val) => {
1254                generate_min_max(field_name, &field_name_str, "not_equals", val)
1255            }
1256            ValidationRule::OneOf(values) => generate_one_of(field_name, &field_name_str, values),
1257
1258            // Collection rules
1259            ValidationRule::MinItems(min) => {
1260                generate_collection_rule(field_name, &field_name_str, "min_items", *min)
1261            }
1262            ValidationRule::MaxItems(max) => {
1263                generate_collection_rule(field_name, &field_name_str, "max_items", *max)
1264            }
1265            ValidationRule::Unique => {
1266                generate_simple_collection_rule(field_name, &field_name_str, "unique")
1267            }
1268        })
1269        .collect();
1270
1271    quote! {
1272        #(#validations)*
1273    }
1274}
1275
1276/// Generate validation code for a single tuple struct field (accessed by index)
1277fn generate_tuple_field_validation(fv: &TupleFieldValidation) -> proc_macro2::TokenStream {
1278    let field_index = syn::Index::from(fv.field_index);
1279    let field_name_str = fv.field_index.to_string();
1280
1281    let validations: Vec<_> = fv
1282        .rules
1283        .iter()
1284        .map(|rule| generate_indexed_field_validation(&field_index, &field_name_str, rule))
1285        .collect();
1286
1287    quote! {
1288        #(#validations)*
1289    }
1290}
1291
1292/// Generate validation code for a single indexed field (used by tuple structs)
1293fn generate_indexed_field_validation(
1294    field_index: &syn::Index,
1295    field_name_str: &str,
1296    rule: &ValidationRule,
1297) -> proc_macro2::TokenStream {
1298    match rule {
1299        ValidationRule::Length { min, max, .. } => {
1300            let rule_expr = match (min, max) {
1301                (Some(min), Some(max)) => {
1302                    quote! { domainstack::rules::min_len(#min).and(domainstack::rules::max_len(#max)) }
1303                }
1304                (Some(min), None) => quote! { domainstack::rules::min_len(#min) },
1305                (None, Some(max)) => quote! { domainstack::rules::max_len(#max) },
1306                (None, None) => return quote! {},
1307            };
1308            quote! {
1309                {
1310                    let rule = #rule_expr;
1311                    if let Err(e) = domainstack::validate(#field_name_str, self.#field_index.as_str(), &rule) {
1312                        err.extend(e);
1313                    }
1314                }
1315            }
1316        }
1317        ValidationRule::Range { min, max, .. } => match (min, max) {
1318            (Some(min), Some(max)) => quote! {
1319                {
1320                    let rule = domainstack::rules::range(#min, #max);
1321                    if let Err(e) = domainstack::validate(#field_name_str, &self.#field_index, &rule) {
1322                        err.extend(e);
1323                    }
1324                }
1325            },
1326            _ => quote! {},
1327        },
1328        ValidationRule::Nested => quote! {
1329            if let Err(e) = self.#field_index.validate() {
1330                err.merge_prefixed(#field_name_str, e);
1331            }
1332        },
1333        ValidationRule::Email => quote! {
1334            {
1335                let rule = domainstack::rules::email();
1336                if let Err(e) = domainstack::validate(#field_name_str, self.#field_index.as_str(), &rule) {
1337                    err.extend(e);
1338                }
1339            }
1340        },
1341        ValidationRule::Url => quote! {
1342            {
1343                let rule = domainstack::rules::url();
1344                if let Err(e) = domainstack::validate(#field_name_str, self.#field_index.as_str(), &rule) {
1345                    err.extend(e);
1346                }
1347            }
1348        },
1349        ValidationRule::MinLen(min) => quote! {
1350            {
1351                let rule = domainstack::rules::min_len(#min);
1352                if let Err(e) = domainstack::validate(#field_name_str, self.#field_index.as_str(), &rule) {
1353                    err.extend(e);
1354                }
1355            }
1356        },
1357        ValidationRule::MaxLen(max) => quote! {
1358            {
1359                let rule = domainstack::rules::max_len(#max);
1360                if let Err(e) = domainstack::validate(#field_name_str, self.#field_index.as_str(), &rule) {
1361                    err.extend(e);
1362                }
1363            }
1364        },
1365        ValidationRule::Alphanumeric => quote! {
1366            {
1367                let rule = domainstack::rules::alphanumeric();
1368                if let Err(e) = domainstack::validate(#field_name_str, self.#field_index.as_str(), &rule) {
1369                    err.extend(e);
1370                }
1371            }
1372        },
1373        ValidationRule::Ascii => quote! {
1374            {
1375                let rule = domainstack::rules::ascii();
1376                if let Err(e) = domainstack::validate(#field_name_str, self.#field_index.as_str(), &rule) {
1377                    err.extend(e);
1378                }
1379            }
1380        },
1381        ValidationRule::NonEmpty => quote! {
1382            {
1383                let rule = domainstack::rules::non_empty();
1384                if let Err(e) = domainstack::validate(#field_name_str, self.#field_index.as_str(), &rule) {
1385                    err.extend(e);
1386                }
1387            }
1388        },
1389        ValidationRule::NonBlank => quote! {
1390            {
1391                let rule = domainstack::rules::non_blank();
1392                if let Err(e) = domainstack::validate(#field_name_str, self.#field_index.as_str(), &rule) {
1393                    err.extend(e);
1394                }
1395            }
1396        },
1397        ValidationRule::MatchesRegex(pattern) => quote! {
1398            {
1399                let rule = domainstack::rules::matches_regex(#pattern);
1400                if let Err(e) = domainstack::validate(#field_name_str, self.#field_index.as_str(), &rule) {
1401                    err.extend(e);
1402                }
1403            }
1404        },
1405        ValidationRule::Min(min) => quote! {
1406            {
1407                let rule = domainstack::rules::min(#min);
1408                if let Err(e) = domainstack::validate(#field_name_str, &self.#field_index, &rule) {
1409                    err.extend(e);
1410                }
1411            }
1412        },
1413        ValidationRule::Max(max) => quote! {
1414            {
1415                let rule = domainstack::rules::max(#max);
1416                if let Err(e) = domainstack::validate(#field_name_str, &self.#field_index, &rule) {
1417                    err.extend(e);
1418                }
1419            }
1420        },
1421        ValidationRule::Positive => quote! {
1422            {
1423                let rule = domainstack::rules::positive();
1424                if let Err(e) = domainstack::validate(#field_name_str, &self.#field_index, &rule) {
1425                    err.extend(e);
1426                }
1427            }
1428        },
1429        ValidationRule::Negative => quote! {
1430            {
1431                let rule = domainstack::rules::negative();
1432                if let Err(e) = domainstack::validate(#field_name_str, &self.#field_index, &rule) {
1433                    err.extend(e);
1434                }
1435            }
1436        },
1437        ValidationRule::NonZero => quote! {
1438            {
1439                let rule = domainstack::rules::non_zero();
1440                if let Err(e) = domainstack::validate(#field_name_str, &self.#field_index, &rule) {
1441                    err.extend(e);
1442                }
1443            }
1444        },
1445        _ => quote! {},
1446    }
1447}
1448
1449/// Generate validation code for enum struct variant fields (accessed by name binding)
1450fn generate_enum_field_validation(
1451    field_name: &syn::Ident,
1452    field_name_str: &str,
1453    rule: &ValidationRule,
1454) -> proc_macro2::TokenStream {
1455    match rule {
1456        ValidationRule::Length { min, max, .. } => {
1457            let rule_expr = match (min, max) {
1458                (Some(min), Some(max)) => {
1459                    quote! { domainstack::rules::min_len(#min).and(domainstack::rules::max_len(#max)) }
1460                }
1461                (Some(min), None) => quote! { domainstack::rules::min_len(#min) },
1462                (None, Some(max)) => quote! { domainstack::rules::max_len(#max) },
1463                (None, None) => return quote! {},
1464            };
1465            quote! {
1466                {
1467                    let rule = #rule_expr;
1468                    if let Err(e) = domainstack::validate(#field_name_str, #field_name.as_str(), &rule) {
1469                        err.extend(e);
1470                    }
1471                }
1472            }
1473        }
1474        ValidationRule::Range { min, max, .. } => match (min, max) {
1475            (Some(min), Some(max)) => quote! {
1476                {
1477                    let rule = domainstack::rules::range(#min, #max);
1478                    if let Err(e) = domainstack::validate(#field_name_str, #field_name, &rule) {
1479                        err.extend(e);
1480                    }
1481                }
1482            },
1483            _ => quote! {},
1484        },
1485        ValidationRule::Nested => quote! {
1486            if let Err(e) = #field_name.validate() {
1487                err.merge_prefixed(#field_name_str, e);
1488            }
1489        },
1490        ValidationRule::Email => quote! {
1491            {
1492                let rule = domainstack::rules::email();
1493                if let Err(e) = domainstack::validate(#field_name_str, #field_name.as_str(), &rule) {
1494                    err.extend(e);
1495                }
1496            }
1497        },
1498        ValidationRule::Url => quote! {
1499            {
1500                let rule = domainstack::rules::url();
1501                if let Err(e) = domainstack::validate(#field_name_str, #field_name.as_str(), &rule) {
1502                    err.extend(e);
1503                }
1504            }
1505        },
1506        ValidationRule::MinLen(min) => quote! {
1507            {
1508                let rule = domainstack::rules::min_len(#min);
1509                if let Err(e) = domainstack::validate(#field_name_str, #field_name.as_str(), &rule) {
1510                    err.extend(e);
1511                }
1512            }
1513        },
1514        ValidationRule::MaxLen(max) => quote! {
1515            {
1516                let rule = domainstack::rules::max_len(#max);
1517                if let Err(e) = domainstack::validate(#field_name_str, #field_name.as_str(), &rule) {
1518                    err.extend(e);
1519                }
1520            }
1521        },
1522        ValidationRule::Alphanumeric => quote! {
1523            {
1524                let rule = domainstack::rules::alphanumeric();
1525                if let Err(e) = domainstack::validate(#field_name_str, #field_name.as_str(), &rule) {
1526                    err.extend(e);
1527                }
1528            }
1529        },
1530        ValidationRule::MatchesRegex(pattern) => quote! {
1531            {
1532                let rule = domainstack::rules::matches_regex(#pattern);
1533                if let Err(e) = domainstack::validate(#field_name_str, #field_name.as_str(), &rule) {
1534                    err.extend(e);
1535                }
1536            }
1537        },
1538        ValidationRule::Min(min) => quote! {
1539            {
1540                let rule = domainstack::rules::min(#min);
1541                if let Err(e) = domainstack::validate(#field_name_str, #field_name, &rule) {
1542                    err.extend(e);
1543                }
1544            }
1545        },
1546        ValidationRule::Max(max) => quote! {
1547            {
1548                let rule = domainstack::rules::max(#max);
1549                if let Err(e) = domainstack::validate(#field_name_str, #field_name, &rule) {
1550                    err.extend(e);
1551                }
1552            }
1553        },
1554        ValidationRule::Positive => quote! {
1555            {
1556                let rule = domainstack::rules::positive();
1557                if let Err(e) = domainstack::validate(#field_name_str, #field_name, &rule) {
1558                    err.extend(e);
1559                }
1560            }
1561        },
1562        ValidationRule::Negative => quote! {
1563            {
1564                let rule = domainstack::rules::negative();
1565                if let Err(e) = domainstack::validate(#field_name_str, #field_name, &rule) {
1566                    err.extend(e);
1567                }
1568            }
1569        },
1570        ValidationRule::NonZero => quote! {
1571            {
1572                let rule = domainstack::rules::non_zero();
1573                if let Err(e) = domainstack::validate(#field_name_str, #field_name, &rule) {
1574                    err.extend(e);
1575                }
1576            }
1577        },
1578        _ => quote! {},
1579    }
1580}
1581
1582/// Generate validation code for enum tuple variant fields (accessed by binding name)
1583fn generate_enum_tuple_field_validation(
1584    binding: &syn::Ident,
1585    field_name_str: &str,
1586    rule: &ValidationRule,
1587) -> proc_macro2::TokenStream {
1588    // Reuse the same logic as enum struct variant fields
1589    generate_enum_field_validation(binding, field_name_str, rule)
1590}
1591
1592fn generate_length_validation(
1593    field_name: &syn::Ident,
1594    field_name_str: &str,
1595    min: &Option<usize>,
1596    max: &Option<usize>,
1597) -> proc_macro2::TokenStream {
1598    let rule = match (min, max) {
1599        (Some(min), Some(max)) => {
1600            quote! { domainstack::rules::min_len(#min).and(domainstack::rules::max_len(#max)) }
1601        }
1602        (Some(min), None) => {
1603            quote! { domainstack::rules::min_len(#min) }
1604        }
1605        (None, Some(max)) => {
1606            quote! { domainstack::rules::max_len(#max) }
1607        }
1608        (None, None) => {
1609            // No constraints - skip
1610            return quote! {};
1611        }
1612    };
1613
1614    quote! {
1615        {
1616            let rule = #rule;
1617            if let Err(e) = domainstack::validate(#field_name_str, self.#field_name.as_str(), &rule) {
1618                err.extend(e);
1619            }
1620        }
1621    }
1622}
1623
1624fn generate_range_validation(
1625    field_name: &syn::Ident,
1626    field_name_str: &str,
1627    min: &Option<proc_macro2::TokenStream>,
1628    max: &Option<proc_macro2::TokenStream>,
1629) -> proc_macro2::TokenStream {
1630    match (min, max) {
1631        (Some(min), Some(max)) => {
1632            quote! {
1633                {
1634                    let rule = domainstack::rules::range(#min, #max);
1635                    if let Err(e) = domainstack::validate(#field_name_str, &self.#field_name, &rule) {
1636                        err.extend(e);
1637                    }
1638                }
1639            }
1640        }
1641        _ => {
1642            // Range requires both min and max
1643            quote! {}
1644        }
1645    }
1646}
1647
1648fn generate_nested_validation(
1649    field_name: &syn::Ident,
1650    field_name_str: &str,
1651) -> proc_macro2::TokenStream {
1652    quote! {
1653        if let Err(e) = self.#field_name.validate() {
1654            err.merge_prefixed(#field_name_str, e);
1655        }
1656    }
1657}
1658
1659fn generate_each_validation(
1660    field_name: &syn::Ident,
1661    field_name_str: &str,
1662    inner_rule: &ValidationRule,
1663) -> proc_macro2::TokenStream {
1664    match inner_rule {
1665        ValidationRule::Nested => {
1666            quote! {
1667                for (i, item) in self.#field_name.iter().enumerate() {
1668                    if let Err(e) = item.validate() {
1669                        let path = domainstack::Path::root().field(#field_name_str).index(i);
1670                        err.merge_prefixed(path, e);
1671                    }
1672                }
1673            }
1674        }
1675        ValidationRule::Length { min, max, .. } => {
1676            let rule = match (min, max) {
1677                (Some(min), Some(max)) => {
1678                    quote! { domainstack::rules::min_len(#min).and(domainstack::rules::max_len(#max)) }
1679                }
1680                (Some(min), None) => {
1681                    quote! { domainstack::rules::min_len(#min) }
1682                }
1683                (None, Some(max)) => {
1684                    quote! { domainstack::rules::max_len(#max) }
1685                }
1686                (None, None) => return quote! {},
1687            };
1688
1689            quote! {
1690                {
1691                    let rule = #rule;
1692                    for (i, item) in self.#field_name.iter().enumerate() {
1693                        let path = domainstack::Path::root().field(#field_name_str).index(i);
1694                        if let Err(e) = domainstack::validate(path, item.as_str(), &rule) {
1695                            err.extend(e);
1696                        }
1697                    }
1698                }
1699            }
1700        }
1701        ValidationRule::Range { min, max, .. } => match (min, max) {
1702            (Some(min), Some(max)) => {
1703                quote! {
1704                    {
1705                        let rule = domainstack::rules::range(#min, #max);
1706                        for (i, item) in self.#field_name.iter().enumerate() {
1707                            let path = domainstack::Path::root().field(#field_name_str).index(i);
1708                            if let Err(e) = domainstack::validate(path, item, &rule) {
1709                                err.extend(e);
1710                            }
1711                        }
1712                    }
1713                }
1714            }
1715            _ => quote! {},
1716        },
1717
1718        // New rich syntax rules - String rules (simple)
1719        ValidationRule::Email => {
1720            quote! {
1721                {
1722                    let rule = domainstack::rules::email();
1723                    for (i, item) in self.#field_name.iter().enumerate() {
1724                        let path = domainstack::Path::root().field(#field_name_str).index(i);
1725                        if let Err(e) = domainstack::validate(path, item.as_str(), &rule) {
1726                            err.extend(e);
1727                        }
1728                    }
1729                }
1730            }
1731        }
1732        ValidationRule::Url => {
1733            quote! {
1734                {
1735                    let rule = domainstack::rules::url();
1736                    for (i, item) in self.#field_name.iter().enumerate() {
1737                        let path = domainstack::Path::root().field(#field_name_str).index(i);
1738                        if let Err(e) = domainstack::validate(path, item.as_str(), &rule) {
1739                            err.extend(e);
1740                        }
1741                    }
1742                }
1743            }
1744        }
1745        ValidationRule::Alphanumeric => {
1746            quote! {
1747                {
1748                    let rule = domainstack::rules::alphanumeric();
1749                    for (i, item) in self.#field_name.iter().enumerate() {
1750                        let path = domainstack::Path::root().field(#field_name_str).index(i);
1751                        if let Err(e) = domainstack::validate(path, item.as_str(), &rule) {
1752                            err.extend(e);
1753                        }
1754                    }
1755                }
1756            }
1757        }
1758        ValidationRule::Ascii => {
1759            quote! {
1760                {
1761                    let rule = domainstack::rules::ascii();
1762                    for (i, item) in self.#field_name.iter().enumerate() {
1763                        let path = domainstack::Path::root().field(#field_name_str).index(i);
1764                        if let Err(e) = domainstack::validate(path, item.as_str(), &rule) {
1765                            err.extend(e);
1766                        }
1767                    }
1768                }
1769            }
1770        }
1771        ValidationRule::AlphaOnly => {
1772            quote! {
1773                {
1774                    let rule = domainstack::rules::alpha_only();
1775                    for (i, item) in self.#field_name.iter().enumerate() {
1776                        let path = domainstack::Path::root().field(#field_name_str).index(i);
1777                        if let Err(e) = domainstack::validate(path, item.as_str(), &rule) {
1778                            err.extend(e);
1779                        }
1780                    }
1781                }
1782            }
1783        }
1784        ValidationRule::NumericString => {
1785            quote! {
1786                {
1787                    let rule = domainstack::rules::numeric_string();
1788                    for (i, item) in self.#field_name.iter().enumerate() {
1789                        let path = domainstack::Path::root().field(#field_name_str).index(i);
1790                        if let Err(e) = domainstack::validate(path, item.as_str(), &rule) {
1791                            err.extend(e);
1792                        }
1793                    }
1794                }
1795            }
1796        }
1797        ValidationRule::NonEmpty => {
1798            quote! {
1799                {
1800                    let rule = domainstack::rules::non_empty();
1801                    for (i, item) in self.#field_name.iter().enumerate() {
1802                        let path = domainstack::Path::root().field(#field_name_str).index(i);
1803                        if let Err(e) = domainstack::validate(path, item.as_str(), &rule) {
1804                            err.extend(e);
1805                        }
1806                    }
1807                }
1808            }
1809        }
1810        ValidationRule::NonBlank => {
1811            quote! {
1812                {
1813                    let rule = domainstack::rules::non_blank();
1814                    for (i, item) in self.#field_name.iter().enumerate() {
1815                        let path = domainstack::Path::root().field(#field_name_str).index(i);
1816                        if let Err(e) = domainstack::validate(path, item.as_str(), &rule) {
1817                            err.extend(e);
1818                        }
1819                    }
1820                }
1821            }
1822        }
1823
1824        // String rules with parameters
1825        ValidationRule::MinLen(min) => {
1826            quote! {
1827                {
1828                    let rule = domainstack::rules::min_len(#min);
1829                    for (i, item) in self.#field_name.iter().enumerate() {
1830                        let path = domainstack::Path::root().field(#field_name_str).index(i);
1831                        if let Err(e) = domainstack::validate(path, item.as_str(), &rule) {
1832                            err.extend(e);
1833                        }
1834                    }
1835                }
1836            }
1837        }
1838        ValidationRule::MaxLen(max) => {
1839            quote! {
1840                {
1841                    let rule = domainstack::rules::max_len(#max);
1842                    for (i, item) in self.#field_name.iter().enumerate() {
1843                        let path = domainstack::Path::root().field(#field_name_str).index(i);
1844                        if let Err(e) = domainstack::validate(path, item.as_str(), &rule) {
1845                            err.extend(e);
1846                        }
1847                    }
1848                }
1849            }
1850        }
1851        ValidationRule::MatchesRegex(pattern) => {
1852            quote! {
1853                {
1854                    let rule = domainstack::rules::matches_regex(#pattern);
1855                    for (i, item) in self.#field_name.iter().enumerate() {
1856                        let path = domainstack::Path::root().field(#field_name_str).index(i);
1857                        if let Err(e) = domainstack::validate(path, item.as_str(), &rule) {
1858                            err.extend(e);
1859                        }
1860                    }
1861                }
1862            }
1863        }
1864
1865        _ => quote! {},
1866    }
1867}
1868
1869fn generate_custom_validation(
1870    field_name: &syn::Ident,
1871    field_name_str: &str,
1872    fn_path: &str,
1873) -> proc_macro2::TokenStream {
1874    let fn_path: proc_macro2::TokenStream = fn_path.parse().unwrap();
1875
1876    quote! {
1877        if let Err(e) = #fn_path(&self.#field_name) {
1878            err.extend(e.prefixed(#field_name_str));
1879        }
1880    }
1881}
1882
1883// Helper functions for new validation rule code generation
1884
1885fn generate_simple_string_rule(
1886    field_name: &syn::Ident,
1887    field_name_str: &str,
1888    rule_fn: &str,
1889) -> proc_macro2::TokenStream {
1890    let rule_fn: proc_macro2::TokenStream = format!("domainstack::rules::{}()", rule_fn)
1891        .parse()
1892        .unwrap();
1893    quote! {
1894        {
1895            let rule = #rule_fn;
1896            if let Err(e) = domainstack::validate(#field_name_str, self.#field_name.as_str(), &rule) {
1897                err.extend(e);
1898            }
1899        }
1900    }
1901}
1902
1903fn generate_min_len(
1904    field_name: &syn::Ident,
1905    field_name_str: &str,
1906    min: usize,
1907) -> proc_macro2::TokenStream {
1908    quote! {
1909        {
1910            let rule = domainstack::rules::min_len(#min);
1911            if let Err(e) = domainstack::validate(#field_name_str, self.#field_name.as_str(), &rule) {
1912                err.extend(e);
1913            }
1914        }
1915    }
1916}
1917
1918fn generate_max_len(
1919    field_name: &syn::Ident,
1920    field_name_str: &str,
1921    max: usize,
1922) -> proc_macro2::TokenStream {
1923    quote! {
1924        {
1925            let rule = domainstack::rules::max_len(#max);
1926            if let Err(e) = domainstack::validate(#field_name_str, self.#field_name.as_str(), &rule) {
1927                err.extend(e);
1928            }
1929        }
1930    }
1931}
1932
1933fn generate_string_param_rule(
1934    field_name: &syn::Ident,
1935    field_name_str: &str,
1936    rule_fn: &str,
1937    param: &str,
1938) -> proc_macro2::TokenStream {
1939    let rule_fn: proc_macro2::TokenStream =
1940        format!("domainstack::rules::{}(\"{}\")", rule_fn, param)
1941            .parse()
1942            .unwrap();
1943    quote! {
1944        {
1945            let rule = #rule_fn;
1946            if let Err(e) = domainstack::validate(#field_name_str, self.#field_name.as_str(), &rule) {
1947                err.extend(e);
1948            }
1949        }
1950    }
1951}
1952
1953fn generate_matches_regex(
1954    field_name: &syn::Ident,
1955    field_name_str: &str,
1956    pattern: &str,
1957) -> proc_macro2::TokenStream {
1958    quote! {
1959        {
1960            let rule = domainstack::rules::matches_regex(#pattern);
1961            if let Err(e) = domainstack::validate(#field_name_str, self.#field_name.as_str(), &rule) {
1962                err.extend(e);
1963            }
1964        }
1965    }
1966}
1967
1968fn generate_simple_numeric_rule(
1969    field_name: &syn::Ident,
1970    field_name_str: &str,
1971    rule_fn: &str,
1972) -> proc_macro2::TokenStream {
1973    let rule_fn: proc_macro2::TokenStream = format!("domainstack::rules::{}()", rule_fn)
1974        .parse()
1975        .unwrap();
1976    quote! {
1977        {
1978            let rule = #rule_fn;
1979            if let Err(e) = domainstack::validate(#field_name_str, &self.#field_name, &rule) {
1980                err.extend(e);
1981            }
1982        }
1983    }
1984}
1985
1986fn generate_min_max(
1987    field_name: &syn::Ident,
1988    field_name_str: &str,
1989    rule_fn: &str,
1990    val: &proc_macro2::TokenStream,
1991) -> proc_macro2::TokenStream {
1992    let rule_fn: proc_macro2::TokenStream = format!("domainstack::rules::{}({})", rule_fn, val)
1993        .parse()
1994        .unwrap();
1995    quote! {
1996        {
1997            let rule = #rule_fn;
1998            if let Err(e) = domainstack::validate(#field_name_str, &self.#field_name, &rule) {
1999                err.extend(e);
2000            }
2001        }
2002    }
2003}
2004
2005fn generate_one_of(
2006    field_name: &syn::Ident,
2007    field_name_str: &str,
2008    values: &[String],
2009) -> proc_macro2::TokenStream {
2010    let values_str = values.iter().map(|v| quote! { #v }).collect::<Vec<_>>();
2011    quote! {
2012        {
2013            let rule = domainstack::rules::one_of(&[#(#values_str),*]);
2014            if let Err(e) = domainstack::validate(#field_name_str, &self.#field_name, &rule) {
2015                err.extend(e);
2016            }
2017        }
2018    }
2019}
2020
2021fn generate_collection_rule(
2022    field_name: &syn::Ident,
2023    field_name_str: &str,
2024    rule_fn: &str,
2025    val: usize,
2026) -> proc_macro2::TokenStream {
2027    let rule_fn: proc_macro2::TokenStream = format!("domainstack::rules::{}({})", rule_fn, val)
2028        .parse()
2029        .unwrap();
2030    quote! {
2031        {
2032            let rule = #rule_fn;
2033            if let Err(e) = domainstack::validate(#field_name_str, &self.#field_name, &rule) {
2034                err.extend(e);
2035            }
2036        }
2037    }
2038}
2039
2040fn generate_simple_collection_rule(
2041    field_name: &syn::Ident,
2042    field_name_str: &str,
2043    rule_fn: &str,
2044) -> proc_macro2::TokenStream {
2045    let rule_fn: proc_macro2::TokenStream = format!("domainstack::rules::{}()", rule_fn)
2046        .parse()
2047        .unwrap();
2048    quote! {
2049        {
2050            let rule = #rule_fn;
2051            if let Err(e) = domainstack::validate(#field_name_str, &self.#field_name, &rule) {
2052                err.extend(e);
2053            }
2054        }
2055    }
2056}
2057
2058fn generate_struct_validation(sv: &StructValidation) -> proc_macro2::TokenStream {
2059    let check_expr: proc_macro2::TokenStream = sv.check.parse().unwrap();
2060    let code = sv
2061        .code
2062        .as_deref()
2063        .unwrap_or("cross_field_validation_failed");
2064    let message = sv
2065        .message
2066        .as_deref()
2067        .unwrap_or("Cross-field validation failed");
2068
2069    let validation_code = quote! {
2070        if !(#check_expr) {
2071            err.violations.push(domainstack::Violation {
2072                path: domainstack::Path::root(),
2073                code: #code,
2074                message: #message.to_string(),
2075                meta: domainstack::Meta::default(),
2076            });
2077        }
2078    };
2079
2080    // Wrap in conditional if 'when' is specified
2081    if let Some(when_expr) = &sv.when {
2082        let when_tokens: proc_macro2::TokenStream = when_expr.parse().unwrap();
2083        quote! {
2084            if #when_tokens {
2085                #validation_code
2086            }
2087        }
2088    } else {
2089        validation_code
2090    }
2091}