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