1use 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 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 let struct_validations = parse_struct_attributes(input)?;
233
234 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 let field_validation_code = field_validations.iter().map(generate_field_validation);
252
253 let struct_validation_code = struct_validations.iter().map(generate_struct_validation);
255
256 let intermediate_name = syn::Ident::new(&format!("{}Intermediate", name), name.span());
258
259 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 let struct_serde_attrs: Vec<_> = input
267 .attrs
268 .iter()
269 .filter(|attr| attr.path().is_ident("serde"))
270 .collect();
271
272 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 let expanded = quote! {
285 #[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 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 let intermediate = #intermediate_name::deserialize(deserializer)?;
304
305 let value = #name {
307 #( #field_names: intermediate.#field_names, )*
308 };
309
310 <#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 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_validation_code)*
325
326 #(#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 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 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 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
453fn 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 let struct_validations = parse_struct_attributes(input)?;
464
465 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 let field_validation_code = field_validations.iter().map(generate_field_validation);
483
484 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_validation_code)*
494
495 #(#struct_validation_code)*
497
498 if err.is_empty() { Ok(()) } else { Err(err) }
499 }
500 }
501 };
502
503 Ok(expanded)
504}
505
506#[derive(Debug)]
508#[allow(dead_code)]
509struct TupleFieldValidation {
510 field_index: usize,
511 field_type: syn::Type,
512 rules: Vec<ValidationRule>,
513}
514
515fn 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 let struct_validations = parse_struct_attributes(input)?;
526
527 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 let field_validation_code = field_validations
544 .iter()
545 .map(generate_tuple_field_validation);
546
547 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_validation_code)*
557
558 #(#struct_validation_code)*
560
561 if err.is_empty() { Ok(()) } else { Err(err) }
562 }
563 }
564 };
565
566 Ok(expanded)
567}
568
569fn 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 let field_names: Vec<_> = fields
587 .named
588 .iter()
589 .map(|f| f.ident.as_ref().unwrap())
590 .collect();
591
592 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 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 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 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 attr.parse_nested_meta(|meta| {
751 if meta.path.is_ident("email") {
753 rules.push(ValidationRule::Email);
754 return Ok(());
755 }
756
757 if meta.path.is_ident("url") {
759 rules.push(ValidationRule::Url);
760 return Ok(());
761 }
762
763 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 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 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 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 if meta.path.is_ident("nested") {
863 rules.push(ValidationRule::Nested);
864 return Ok(());
865 }
866
867 if meta.path.is_ident("each") {
869 meta.parse_nested_meta(|nested| {
870 if nested.path.is_ident("nested") {
872 rules.push(ValidationRule::Each(Box::new(ValidationRule::Nested)));
873 return Ok(());
874 }
875
876 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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
1276fn 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
1292fn 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
1449fn 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
1582fn generate_enum_tuple_field_validation(
1584 binding: &syn::Ident,
1585 field_name_str: &str,
1586 rule: &ValidationRule,
1587) -> proc_macro2::TokenStream {
1588 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 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 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 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 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
1883fn 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 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}