diff --git a/packages/fortifier-macros/src/validate/enum.rs b/packages/fortifier-macros/src/validate/enum.rs index 7a199e4..b74bce3 100644 --- a/packages/fortifier-macros/src/validate/enum.rs +++ b/packages/fortifier-macros/src/validate/enum.rs @@ -1,6 +1,6 @@ use proc_macro2::TokenStream; use quote::{format_ident, quote}; -use syn::{DataEnum, DeriveInput, Ident, Result, Variant, Visibility}; +use syn::{DataEnum, DeriveInput, Generics, Ident, Result, Variant, Visibility}; use crate::{ validate::{ @@ -30,6 +30,7 @@ impl<'a> ValidateEnum<'a> { for variant in &data.variants { result.variants.push(ValidateEnumVariant::parse( &input.vis, + &input.generics, result.ident, result.error_ident.clone(), variant, @@ -109,6 +110,7 @@ pub struct ValidateEnumVariant<'a> { impl<'a> ValidateEnumVariant<'a> { pub fn parse( visibility: &'a Visibility, + generics: &'a Generics, enum_ident: &'a Ident, enum_error_ident: Ident, variant: &'a Variant, @@ -119,6 +121,7 @@ impl<'a> ValidateEnumVariant<'a> { ident: &variant.ident, fields: ValidateFields::parse( visibility, + generics, format_ident!("{}{}", enum_ident, variant.ident), &variant.fields, )?, diff --git a/packages/fortifier-macros/src/validate/field.rs b/packages/fortifier-macros/src/validate/field.rs index e8a4433..69e4a29 100644 --- a/packages/fortifier-macros/src/validate/field.rs +++ b/packages/fortifier-macros/src/validate/field.rs @@ -1,7 +1,7 @@ use convert_case::{Case, Casing}; use proc_macro2::{Literal, TokenStream}; use quote::{ToTokens, format_ident, quote}; -use syn::{Error, Field, Ident, Result, Visibility}; +use syn::{Error, Field, Generics, Ident, Result, Visibility}; use crate::{ validate::{ @@ -46,6 +46,7 @@ pub struct ValidateField<'a> { impl<'a> ValidateField<'a> { pub fn parse( visibility: &'a Visibility, + generics: &'a Generics, type_prefix: &Ident, ident: LiteralOrIdent, field: &Field, @@ -128,10 +129,9 @@ impl<'a> ValidateField<'a> { } } - // TODO: Use enum/struct generics to determine if a generic field type supports nested validation. if !skip_nested && result.validations.is_empty() - && let Some(nested_type) = should_validate_type(&field.ty) + && let Some(nested_type) = should_validate_type(generics, &field.ty) { if let KnownOrUnknown::Known(nested_type) = nested_type { result.validations.push(Box::new(Nested::new(nested_type))); diff --git a/packages/fortifier-macros/src/validate/fields.rs b/packages/fortifier-macros/src/validate/fields.rs index 764e616..f724f58 100644 --- a/packages/fortifier-macros/src/validate/fields.rs +++ b/packages/fortifier-macros/src/validate/fields.rs @@ -2,7 +2,7 @@ use std::iter::empty; use proc_macro2::{Literal, TokenStream}; use quote::quote; -use syn::{Fields, FieldsNamed, FieldsUnnamed, Ident, Result, Visibility}; +use syn::{Fields, FieldsNamed, FieldsUnnamed, Generics, Ident, Result, Visibility}; use crate::{ validate::{ @@ -22,14 +22,19 @@ pub enum ValidateFields<'a> { } impl<'a> ValidateFields<'a> { - pub fn parse(visibility: &'a Visibility, ident: Ident, fields: &'a Fields) -> Result { + pub fn parse( + visibility: &'a Visibility, + generics: &'a Generics, + ident: Ident, + fields: &'a Fields, + ) -> Result { Ok(match fields { - Fields::Named(fields) => { - Self::Named(ValidateNamedFields::parse(visibility, ident, fields)?) - } - Fields::Unnamed(fields) => { - Self::Unnamed(ValidateUnnamedFields::parse(visibility, ident, fields)?) - } + Fields::Named(fields) => Self::Named(ValidateNamedFields::parse( + visibility, generics, ident, fields, + )?), + Fields::Unnamed(fields) => Self::Unnamed(ValidateUnnamedFields::parse( + visibility, generics, ident, fields, + )?), Fields::Unit => Self::Unit(ValidateUnitFields::parse(visibility, ident)?), }) } @@ -94,7 +99,12 @@ pub struct ValidateNamedFields<'a> { } impl<'a> ValidateNamedFields<'a> { - fn parse(visibility: &'a Visibility, ident: Ident, fields: &'a FieldsNamed) -> Result { + fn parse( + visibility: &'a Visibility, + generics: &'a Generics, + ident: Ident, + fields: &'a FieldsNamed, + ) -> Result { let error_ident = format_error_ident(&ident); let mut result = Self { @@ -111,6 +121,7 @@ impl<'a> ValidateNamedFields<'a> { result.fields.push(ValidateField::parse( visibility, + generics, &result.ident, LiteralOrIdent::Ident(field_ident.clone()), field, @@ -173,7 +184,12 @@ pub struct ValidateUnnamedFields<'a> { } impl<'a> ValidateUnnamedFields<'a> { - fn parse(visibility: &'a Visibility, ident: Ident, fields: &'a FieldsUnnamed) -> Result { + fn parse( + visibility: &'a Visibility, + generics: &'a Generics, + ident: Ident, + fields: &'a FieldsUnnamed, + ) -> Result { let error_ident = format_error_ident(&ident); let mut result = Self { @@ -186,6 +202,7 @@ impl<'a> ValidateUnnamedFields<'a> { for (index, field) in fields.unnamed.iter().enumerate() { result.fields.push(ValidateField::parse( visibility, + generics, &result.ident, LiteralOrIdent::Literal(Literal::usize_unsuffixed(index)), field, diff --git a/packages/fortifier-macros/src/validate/struct.rs b/packages/fortifier-macros/src/validate/struct.rs index 1be43eb..44602bb 100644 --- a/packages/fortifier-macros/src/validate/struct.rs +++ b/packages/fortifier-macros/src/validate/struct.rs @@ -13,7 +13,12 @@ pub struct ValidateStruct<'a> { impl<'a> ValidateStruct<'a> { pub fn parse(input: &'a DeriveInput, data: &'a DataStruct) -> Result { Ok(ValidateStruct { - fields: ValidateFields::parse(&input.vis, input.ident.clone(), &data.fields)?, + fields: ValidateFields::parse( + &input.vis, + &input.generics, + input.ident.clone(), + &data.fields, + )?, }) } diff --git a/packages/fortifier-macros/src/validate/type.rs b/packages/fortifier-macros/src/validate/type.rs index ffaa0d7..d73b3b5 100644 --- a/packages/fortifier-macros/src/validate/type.rs +++ b/packages/fortifier-macros/src/validate/type.rs @@ -1,29 +1,145 @@ use proc_macro2::TokenStream; use quote::{ToTokens, quote}; -use syn::{GenericArgument, Path, PathArguments, Type, TypeParamBound}; +use syn::{ + GenericArgument, Generics, Path, PathArguments, PathSegment, Type, TypeParamBound, + WherePredicate, punctuated::Punctuated, token::PathSep, +}; use crate::validate::error::format_error_ident; -const PRIMITIVE_AND_BUILT_IN_TYPES: [&str; 18] = [ - "bool", "i8", "i16", "i32", "i64", "i128", "isize", "u8", "u16", "u32", "u64", "u128", "usize", - "f32", "f64", "char", "str", "String", +/// Primitive and built-in types. +/// +/// Only single identifiers without generics. +const PRIMITIVE_AND_BUILT_IN_TYPES: [&str; 56] = [ + "bool", + "i8", + "i16", + "i32", + "i64", + "i128", + "isize", + "u8", + "u16", + "u32", + "u64", + "u128", + "usize", + "f32", + "f64", + "char", + "str", + "String", + "AtomicBool", + "AtomicI16", + "AtomicI32", + "AtomicI64", + "AtomicI8", + "AtomicIsize", + "AtomicPtr", + "AtomicU16", + "AtomicU32", + "AtomicU64", + "AtomicU8", + "AtomicUsize", + "CStr", + "CString", + "Duration", + "Instant", + "IpAddr", + "Ipv4Addr", + "Ipv6Adr", + "NonZeroI128", + "NonZeroI16", + "NonZeroI32", + "NonZeroI64", + "NonZeroI8", + "NonZeroIsize", + "NonZeroU128", + "NonZeroU16", + "NonZeroU32", + "NonZeroU64", + "NonZeroU8", + "NonZeroUsize", + "OsStr", + "OsString", + "Path", + "PathBuf", + "SocketAddr", + "SocketAddrV4", + "SocketAddrV6", ]; -const CONTAINER_TYPES: [&str; 6] = [ +/// Built-in types. +/// +/// Only paths and single identifiers with generics. +const BUILT_IN_TYPES: [&str; 40] = [ + "PhantomData", + "std::ffi::CStr", + "std::ffi::CString", + "std::ffi::OsStr", + "std::ffi::OsString", + "std::marker::PhantomData", + "std::net::IpAddr", + "std::net::Ipv4Addr", + "std::net::Ipv6Adr", + "std::net::SocketAddr", + "std::net::SocketAddrV4", + "std::net::SocketAddrV6", + "std::num::NonZeroI128", + "std::num::NonZeroI16", + "std::num::NonZeroI32", + "std::num::NonZeroI64", + "std::num::NonZeroI8", + "std::num::NonZeroIsize", + "std::num::NonZeroU128", + "std::num::NonZeroU16", + "std::num::NonZeroU32", + "std::num::NonZeroU64", + "std::num::NonZeroU8", + "std::num::NonZeroUsize", + "std::path::Path", + "std::path::PathBuf", + "std::sync::atomic::AtomicBool", + "std::sync::atomic::AtomicI16", + "std::sync::atomic::AtomicI32", + "std::sync::atomic::AtomicI64", + "std::sync::atomic::AtomicI8", + "std::sync::atomic::AtomicIsize", + "std::sync::atomic::AtomicPtr", + "std::sync::atomic::AtomicU16", + "std::sync::atomic::AtomicU32", + "std::sync::atomic::AtomicU64", + "std::sync::atomic::AtomicU8", + "std::sync::atomic::AtomicUsize", + "std::time::Duration", + "std::time::Instant", +]; + +/// Container types. +const CONTAINER_TYPES: [&str; 12] = [ "Arc", + "NonZero", "Option", "Rc", + "Saturating", + "Wrapping", + "std::num::NonZero", + "std::num::Saturating", + "std::num::Wrapping", "std::option::Option", "std::rc::Rc", "std::sync::Arc", ]; -const INDEXED_CONTAINER_TYPES: [&str; 10] = [ +/// Indexed container types. +const INDEXED_CONTAINER_TYPES: [&str; 12] = [ "BTreeSet", "HashSet", + "IndexSet", "LinkedList", "Vec", "VecDeque", + "indexmap::set::IndexMap", "std::collections::BTreeSet", "std::collections::HashSet", "std::collections::LinkedList", @@ -31,15 +147,17 @@ const INDEXED_CONTAINER_TYPES: [&str; 10] = [ "std::vec::Vec", ]; -const KEYED_CONTAINER_TYPES: [&str; 4] = [ +/// Keyed container types. +const KEYED_CONTAINER_TYPES: [&str; 6] = [ "BTreeMap", "HashMap", + "IndexMap", + "indexmap::map::IndexMap", "std::collections::BTreeMap", "std::collections::HashMap", ]; fn path_to_string(path: &Path) -> String { - // TODO: This is probably slow, replace with comparisons. path.segments .iter() .map(|segment| segment.ident.to_string()) @@ -52,37 +170,70 @@ fn is_validate_path(path: &Path) -> bool { path_string == "Validate" || path_string == "fortifier::Validate" } -fn should_validate_generic_argument(arg: &GenericArgument) -> Option> { +fn should_validate_generic_argument( + generics: &Generics, + arg: &GenericArgument, +) -> Option> { match arg { GenericArgument::Lifetime(_) => Some(KnownOrUnknown::Unknown), - GenericArgument::Type(r#type) => should_validate_type(r#type), - GenericArgument::Const(_expr) => todo!(), - GenericArgument::AssocType(_assoc_type) => todo!(), - GenericArgument::AssocConst(_assoc_const) => todo!(), - GenericArgument::Constraint(_constraint) => todo!(), + GenericArgument::Type(r#type) => should_validate_type(generics, r#type), + // TODO: Const. + GenericArgument::Const(_expr) => Some(KnownOrUnknown::Unknown), + // TODO: Associated type. + GenericArgument::AssocType(_assoc_type) => Some(KnownOrUnknown::Unknown), + // TODO: Associated const. + GenericArgument::AssocConst(_assoc_const) => Some(KnownOrUnknown::Unknown), + // TODO: Constraint. + GenericArgument::Constraint(_constraint) => Some(KnownOrUnknown::Unknown), _ => Some(KnownOrUnknown::Unknown), } } -fn should_validate_path(path: &Path) -> Option> { +fn should_validate_type_param_bounds<'a>( + mut bounds: impl Iterator, +) -> Option> { + bounds + .any(|bound| matches!(bound, TypeParamBound::Trait(bound) if is_validate_path(&bound.path))) + .then_some(KnownOrUnknown::Unknown) +} + +fn should_validate_path(generics: &Generics, path: &Path) -> Option> { if let Some(ident) = path.get_ident() { - return if PRIMITIVE_AND_BUILT_IN_TYPES.contains(&ident.to_string().as_str()) { - None - } else { - Some(KnownOrUnknown::Known( - format_error_ident(ident).to_token_stream(), - )) - }; + if PRIMITIVE_AND_BUILT_IN_TYPES.contains(&ident.to_string().as_str()) { + return None; + } + + if let Some(param) = generics.type_params().find(|param| param.ident == *ident) { + return should_validate_type_param_bounds(param.bounds.iter()).or_else(|| { + generics.where_clause.as_ref().and_then(|where_clause| { + where_clause.predicates.iter().find_map(|predicate| { + if let WherePredicate::Type(predicate) = predicate + && let Type::Path(predicate_type) = &predicate.bounded_ty + && predicate_type.path.is_ident(ident) + { + should_validate_type_param_bounds(predicate.bounds.iter()) + } else { + None + } + }) + }) + }); + } } + let path_string = path_to_string(path); let path_string = path_string.as_str(); + if BUILT_IN_TYPES.contains(&path_string) { + return None; + } + if CONTAINER_TYPES.contains(&path_string) && let Some(segment) = path.segments.last() && let PathArguments::AngleBracketed(arguments) = &segment.arguments && let Some(argument) = arguments.args.first() { - return should_validate_generic_argument(argument); + return should_validate_generic_argument(generics, argument); } if INDEXED_CONTAINER_TYPES.contains(&path_string) @@ -90,11 +241,13 @@ fn should_validate_path(path: &Path) -> Option> { && let PathArguments::AngleBracketed(arguments) = &segment.arguments && let Some(argument) = arguments.args.first() { - return should_validate_generic_argument(argument).map(|error_type| match error_type { - KnownOrUnknown::Known(error_type) => { - KnownOrUnknown::Known(quote!(::fortifier::IndexedValidationError<#error_type>)) + return should_validate_generic_argument(generics, argument).map(|error_type| { + match error_type { + KnownOrUnknown::Known(error_type) => { + KnownOrUnknown::Known(quote!(::fortifier::IndexedValidationError<#error_type>)) + } + KnownOrUnknown::Unknown => KnownOrUnknown::Unknown, } - KnownOrUnknown::Unknown => KnownOrUnknown::Unknown, }); } @@ -105,12 +258,28 @@ fn should_validate_path(path: &Path) -> Option> { && !arguments .args .iter() - .all(|arg| should_validate_generic_argument(arg).is_some()) + .all(|arg| should_validate_generic_argument(generics, arg).is_some()) { return None; } - Some(KnownOrUnknown::Unknown) + let path = Punctuated::::from_iter( + path.segments + .iter() + .take(path.segments.len() - 1) + .cloned() + .chain( + path.segments + .iter() + .skip(path.segments.len() - 1) + .map(|segment| PathSegment { + ident: format_error_ident(&segment.ident), + arguments: segment.arguments.clone(), + }), + ), + ); + + Some(KnownOrUnknown::Known(path.to_token_stream())) } pub enum KnownOrUnknown { @@ -118,27 +287,28 @@ pub enum KnownOrUnknown { Unknown, } -pub fn should_validate_type(r#type: &Type) -> Option> { +pub fn should_validate_type( + generics: &Generics, + r#type: &Type, +) -> Option> { match r#type { - Type::Array(r#type) => should_validate_type(&r#type.elem), + Type::Array(r#type) => should_validate_type(generics, &r#type.elem), Type::BareFn(_) => None, - Type::Group(r#type) => should_validate_type(&r#type.elem), + Type::Group(r#type) => should_validate_type(generics, &r#type.elem), Type::ImplTrait(r#type) => r#type.bounds.iter().any( |bound| matches!(bound, TypeParamBound::Trait(bound) if is_validate_path(&bound.path)), ).then_some(KnownOrUnknown::Unknown), Type::Infer(_) => Some(KnownOrUnknown::Unknown), Type::Macro(_) => Some(KnownOrUnknown::Unknown), Type::Never(_) => None, - Type::Paren(r#type) => should_validate_type(&r#type.elem), - Type::Path(r#type) => should_validate_path(&r#type.path), - Type::Ptr(r#type) => should_validate_type(&r#type.elem), - Type::Reference(r#type) => should_validate_type(&r#type.elem), - Type::Slice(r#type) => should_validate_type(&r#type.elem), - Type::TraitObject(r#type) => r#type.bounds.iter().any( - |bound| matches!(bound, TypeParamBound::Trait(bound) if is_validate_path(&bound.path)), - ).then_some(KnownOrUnknown::Unknown), + Type::Paren(r#type) => should_validate_type(generics, &r#type.elem), + Type::Path(r#type) => should_validate_path(generics, &r#type.path), + Type::Ptr(r#type) => should_validate_type(generics, &r#type.elem), + Type::Reference(r#type) => should_validate_type(generics,&r#type.elem), + Type::Slice(r#type) => should_validate_type(generics, &r#type.elem), + Type::TraitObject(r#type) => should_validate_type_param_bounds(r#type.bounds.iter()), Type::Tuple(r#type) => { - (!r#type.elems.is_empty() && r#type.elems.iter().all(|r#type| should_validate_type(r#type).is_some())).then_some(KnownOrUnknown::Unknown) + (!r#type.elems.is_empty() && r#type.elems.iter().all(|r#type| should_validate_type(generics, r#type).is_some())).then_some(KnownOrUnknown::Unknown) } Type::Verbatim(_) => None, _ => None, @@ -149,11 +319,20 @@ pub fn should_validate_type(r#type: &Type) -> Option mod tests { use proc_macro2::TokenStream; use quote::quote; + use syn::{GenericParam, Generics, punctuated::Punctuated}; use super::should_validate_type; fn validate(tokens: TokenStream) -> bool { - should_validate_type(&syn::parse2(tokens).expect("valid type")).is_some() + should_validate_type( + &Generics::default(), + &syn::parse2(tokens).expect("valid type"), + ) + .is_some() + } + + fn validate_with_generics(tokens: TokenStream, generics: Generics) -> bool { + should_validate_type(&generics, &syn::parse2(tokens).expect("valid type")).is_some() } #[test] @@ -235,4 +414,62 @@ mod tests { assert!(!validate(quote!(impl Serialize))); assert!(!validate(quote!(dyn Serialize))); } + + #[test] + fn should_validate_with_generics() { + assert!(validate_with_generics( + quote!(T), + Generics { + lt_token: Default::default(), + params: Punctuated::from_iter([ + syn::parse2::(quote!(T: Validate)).expect("valid generic param") + ]), + gt_token: Default::default(), + where_clause: None + } + )); + + assert!(validate_with_generics( + quote!(T), + Generics { + lt_token: Default::default(), + params: Punctuated::from_iter([ + syn::parse2::(quote!(T)).expect("valid generic param") + ]), + gt_token: Default::default(), + where_clause: Some( + syn::parse2(quote!(where T: Validate)).expect("valid where clause") + ) + } + )); + } + + #[test] + fn should_not_validate_with_generics() { + assert!(!validate_with_generics( + quote!(T), + Generics { + lt_token: Default::default(), + params: Punctuated::from_iter([ + syn::parse2::(quote!(T: PartialEq)).expect("valid generic param") + ]), + gt_token: Default::default(), + where_clause: None + } + )); + + assert!(!validate_with_generics( + quote!(T), + Generics { + lt_token: Default::default(), + params: Punctuated::from_iter([ + syn::parse2::(quote!(T)).expect("valid generic param") + ]), + gt_token: Default::default(), + where_clause: Some( + syn::parse2(quote!(where T: PartialEq)).expect("valid where clause") + ) + } + )); + } } diff --git a/packages/fortifier/src/lib.rs b/packages/fortifier/src/lib.rs index 303f7a4..197eceb 100644 --- a/packages/fortifier/src/lib.rs +++ b/packages/fortifier/src/lib.rs @@ -5,6 +5,7 @@ mod error; mod error_code; mod integrations; +mod types; mod validate; mod validations; diff --git a/packages/fortifier/src/types.rs b/packages/fortifier/src/types.rs new file mode 100644 index 0000000..7312c73 --- /dev/null +++ b/packages/fortifier/src/types.rs @@ -0,0 +1,6 @@ +#[cfg(feature = "chrono")] +mod chrono; +#[cfg(feature = "decimal")] +mod decimal; +#[cfg(feature = "uuid")] +mod uuid; diff --git a/packages/fortifier/src/types/chrono.rs b/packages/fortifier/src/types/chrono.rs new file mode 100644 index 0000000..f94f56c --- /dev/null +++ b/packages/fortifier/src/types/chrono.rs @@ -0,0 +1,40 @@ +//! Infallible validation implementations for [`chrono`] types. +//! +//! This prevents having to specify `#[validate(skip)]` for these types. + +use std::convert::Infallible; + +use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, TimeDelta, TimeZone}; + +use crate::{ + error::ValidationErrors, + validate::{Validate, ValidateWithContext}, + validate_ok, +}; + +validate_ok!(NaiveDate); +validate_ok!(NaiveDateTime); +validate_ok!(NaiveTime); +validate_ok!(TimeDelta); + +impl ValidateWithContext for DateTime { + type Context = (); + type Error = Infallible; + + fn validate_sync_with_context( + &self, + _context: &Self::Context, + ) -> Result<(), ValidationErrors> { + Ok(()) + } + + fn validate_async_with_context( + &self, + _context: &Self::Context, + ) -> ::std::pin::Pin>> + Send>> + { + Box::pin(async { Ok(()) }) + } +} + +impl Validate for DateTime {} diff --git a/packages/fortifier/src/types/decimal.rs b/packages/fortifier/src/types/decimal.rs new file mode 100644 index 0000000..b49cfc2 --- /dev/null +++ b/packages/fortifier/src/types/decimal.rs @@ -0,0 +1,9 @@ +//! Infallible validation implementations for [`rust_decimal`] types. +//! +//! This prevents having to specify `#[validate(skip)]` for these types. + +use rust_decimal::Decimal; + +use crate::validate_ok; + +validate_ok!(Decimal); diff --git a/packages/fortifier/src/types/uuid.rs b/packages/fortifier/src/types/uuid.rs new file mode 100644 index 0000000..ef9c58a --- /dev/null +++ b/packages/fortifier/src/types/uuid.rs @@ -0,0 +1,9 @@ +//! Infallible validation implementations for [`uuid`] types. +//! +//! This prevents having to specify `#[validate(skip)]` for these types. + +use uuid::Uuid; + +use crate::validate_ok; + +validate_ok!(Uuid); diff --git a/packages/fortifier/src/validate.rs b/packages/fortifier/src/validate.rs index 32a208d..0e955a2 100644 --- a/packages/fortifier/src/validate.rs +++ b/packages/fortifier/src/validate.rs @@ -70,11 +70,42 @@ pub trait Validate: ValidateWithContext { } } +/// Generate an infallible validate implementation for a type. +#[macro_export] +macro_rules! validate_ok { + ($type:ty) => { + impl $crate::ValidateWithContext for $type { + type Context = (); + type Error = ::std::convert::Infallible; + + fn validate_sync_with_context( + &self, + _context: &Self::Context, + ) -> Result<(), $crate::ValidationErrors> { + Ok(()) + } + + fn validate_async_with_context( + &self, + _context: &Self::Context, + ) -> ::std::pin::Pin< + Box>> + Send>, + > { + Box::pin(async { Ok(()) }) + } + } + + impl $crate::Validate for $type {} + }; +} + +/// Generate a dereference validate implementation for a type. +#[macro_export] macro_rules! validate_with_deref { ($type:ty) => { - impl ValidateWithContext for $type + impl $crate::ValidateWithContext for $type where - T: ValidateWithContext, + T: $crate::ValidateWithContext, { type Context = T::Context; type Error = T::Error; @@ -82,15 +113,16 @@ macro_rules! validate_with_deref { fn validate_sync_with_context( &self, context: &Self::Context, - ) -> Result<(), ValidationErrors> { + ) -> Result<(), $crate::ValidationErrors> { T::validate_sync_with_context(self, context) } fn validate_async_with_context( &self, context: &Self::Context, - ) -> Pin>> + Send>> - { + ) -> ::std::pin::Pin< + Box>> + Send>, + > { T::validate_async_with_context(self, context) } }