diff --git a/dummy/src/main.rs b/dummy/src/main.rs index bccf1bd..912d9cc 100644 --- a/dummy/src/main.rs +++ b/dummy/src/main.rs @@ -1,6 +1,27 @@ use nutype::nutype; -#[nutype(validate(len_char_min = 5), default = "Anonymous", derive(Default))] -pub struct Name(String); +#[nutype( + sanitize(with = |mut v| { v.sort(); v }), + validate(predicate = |vec| !vec.is_empty()), + derive(Debug, AsRef, PartialEq, Deref), +)] +struct SortedNotEmptyVec(Vec); -fn main() {} +fn main() { + // Empty list is not allowed + assert_eq!( + SortedNotEmptyVec::<&str>::try_new(vec![]), + Err(SortedNotEmptyVecError::PredicateViolated) + ); + + let wise_friends = + SortedNotEmptyVec::try_new(vec!["Seneca", "Zeno", "Socrates", "Epictetus", "Plato"]) + .unwrap(); + + assert_eq!( + wise_friends.as_ref(), + &["Epictetus", "Plato", "Seneca", "Socrates", "Zeno"] + ); + + assert_eq!(wise_friends.len(), 5); +} diff --git a/nutype_macros/src/common/gen/traits.rs b/nutype_macros/src/common/gen/traits.rs index caafec5..1f98ba0 100644 --- a/nutype_macros/src/common/gen/traits.rs +++ b/nutype_macros/src/common/gen/traits.rs @@ -22,7 +22,7 @@ pub struct GeneratedTraits { /// Split traits into 2 groups for generation: /// * Transparent traits can be simply derived, e.g. `derive(Debug)`. -/// * Irregular traits requires implementation to be generated. +/// * Irregular traits requires custom implementation to be generated. pub enum GeneratableTrait { Transparent(TransparentTrait), Irregular(IrregularTrait), @@ -81,13 +81,15 @@ pub fn gen_impl_trait_as_ref( generics: &Generics, inner_type: impl ToTokens, ) -> TokenStream { + let generics_without_bounds = strip_trait_bounds_on_generics(generics); + quote! { - impl #generics ::core::convert::AsRef<#inner_type> for #type_name #generics { - #[inline] - fn as_ref(&self) -> &#inner_type { - &self.0 - } - } + impl #generics ::core::convert::AsRef<#inner_type> for #type_name #generics_without_bounds { // impl AsRef> for Collection { + #[inline] // #[inline] + fn as_ref(&self) -> &#inner_type { // fn as_ref(&self) -> &Vec { + &self.0 // &self.0 + } // } + } // } } } @@ -96,15 +98,17 @@ pub fn gen_impl_trait_deref( generics: &Generics, inner_type: impl ToTokens, ) -> TokenStream { - quote! { - impl #generics ::core::ops::Deref for #type_name #generics { - type Target = #inner_type; + let generics_without_bounds = strip_trait_bounds_on_generics(generics); - #[inline] - fn deref(&self) -> &Self::Target { - &self.0 - } - } + quote! { + impl #generics ::core::ops::Deref for #type_name #generics_without_bounds { // impl Deref for Collection { + type Target = #inner_type; // type Target = Vec; + // + #[inline] // #[inline] + fn deref(&self) -> &Self::Target { // fn deref(&self) -> &Self::Target { + &self.0 // &self.0 + } // } + } // } } } @@ -135,13 +139,15 @@ pub fn gen_impl_trait_borrow( generics: &Generics, borrowed_type: impl ToTokens, ) -> TokenStream { + let generics_without_bounds = strip_trait_bounds_on_generics(generics); + quote! { - impl #generics ::core::borrow::Borrow<#borrowed_type> for #type_name #generics { - #[inline] - fn borrow(&self) -> &#borrowed_type { - &self.0 - } - } + impl #generics ::core::borrow::Borrow<#borrowed_type> for #type_name #generics_without_bounds { // impl Borrow> for Collection { + #[inline] // #[inline] + fn borrow(&self) -> &#borrowed_type { // fn borrow(&self) -> &Vec { + &self.0 // &self.0 + } // } + } // } } } @@ -150,13 +156,15 @@ pub fn gen_impl_trait_from( generics: &Generics, inner_type: impl ToTokens, ) -> TokenStream { + let generics_without_bounds = strip_trait_bounds_on_generics(generics); + quote! { - impl #generics ::core::convert::From<#inner_type> for #type_name #generics { - #[inline] - fn from(raw_value: #inner_type) -> Self { - Self::new(raw_value) - } - } + impl #generics ::core::convert::From<#inner_type> for #type_name #generics_without_bounds { // impl From> for Collection { + #[inline] // #[inline] + fn from(raw_value: #inner_type) -> Self { // fn from(raw_value: Vec) -> Self { + Self::new(raw_value) // Self::new(raw_value) + } // } + } // } } } diff --git a/test_suite/tests/any.rs b/test_suite/tests/any.rs index c3dd6f4..877a7ea 100644 --- a/test_suite/tests/any.rs +++ b/test_suite/tests/any.rs @@ -700,8 +700,22 @@ mod with_generics { } } + #[test] + fn test_generic_from_with_bounds_and_sanitization() { + #[nutype( + sanitize(with = |mut v| { v.sort(); v }), + derive(Debug, From), + )] + struct Sorted(Vec); + + let sorted: Sorted = Sorted::from(vec![3, 1, 2]); + assert_eq!(sorted.into_inner(), vec![1, 2, 3]); + } + #[test] fn test_generic_from_str_without_validation() { + // Note: the code generate for FromStr relies on "associated type bounds" feature, which is + // stabilized only in 1.79. #[nutype(derive(Debug, FromStr))] struct Parseable(T); @@ -835,6 +849,53 @@ mod with_generics { assert_eq!(squares.as_ref(), &inner_map); } + #[test] + fn test_derive_as_ref_with_generic_boundaries_and_validation_and_sanitization() { + #[nutype( + sanitize(with = |mut v| { v.sort(); v }), + validate(predicate = |vec| !vec.is_empty()), + derive(Debug, AsRef, PartialEq), + )] + struct Friends(Vec); + + assert_eq!( + Friends::<&str>::try_new(vec![]), + Err(FriendsError::PredicateViolated) + ); + + let wise_friends = Friends::try_new(vec!["Seneca", "Zeno", "Aristotle"]).unwrap(); + assert_eq!(wise_friends.as_ref(), &["Aristotle", "Seneca", "Zeno"]); + } + + #[test] + fn test_derive_deref_with_generic_boundaries_and_validation_and_sanitization() { + #[nutype( + sanitize(with = |mut v| { v.sort(); v }), + validate(predicate = |vec| !vec.is_empty()), + derive(Debug, Deref), + )] + struct Penguins(Vec); + + let penguins = Penguins::try_new(vec!["Tux", "Chilly Willy"]).unwrap(); + assert_eq!(penguins.len(), 2); + } + + #[test] + fn test_derive_borrow_with_generic_boundaries_and_validation_and_sanitization() { + use std::borrow::Borrow; + + #[nutype( + sanitize(with = |mut v| { v.sort(); v }), + validate(predicate = |vec| !vec.is_empty()), + derive(Debug, Borrow), + )] + struct Heroes(Vec); + + let heroes = Heroes::try_new(vec!["Spiderman", "Batman"]).unwrap(); + let borrowed_heroes: &Vec<&str> = heroes.borrow(); + assert_eq!(borrowed_heroes, &vec!["Batman", "Spiderman"]); + } + #[test] fn test_derive_default_with_generics() { #[nutype(