diff --git a/CHANGELOG.md b/CHANGELOG.md index 77289a5b..259d556a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ - **[FEATURE]** Friendlier error when a `#[nutype(...)]` attribute is mistyped: suggests the closest match (e.g. `validte` -> `validate`) and lists the available nutype attributes (see [#240](https://github.com/greyblake/nutype/issues/240)). - **[FIX]** Fix misleading error for value-type mismatches in validators (see [#241](https://github.com/greyblake/nutype/issues/241)). - **[FIX]** Improve rust-analyzer resilience: when `#[nutype(...)]` arguments fail to parse (e.g. while still being typed), emit a best-effort type skeleton alongside the error so the newtype stays resolvable and downstream completions keep working (see [#178](https://github.com/greyblake/nutype/issues/178)). +- **[FIX]** Correct the validation error `Display` message for float newtypes: `less` now reads "The value must be less than ..." and `less_or_equal` reads "The value must be less or equal to ..." (the two were previously swapped). Integer and decimal were already correct. +- **[INTERNAL]** Consolidate the integer, float and decimal backends onto a shared numeric code-generation and validation layer (`common/generate/numeric.rs` and shared helpers in `common/validate.rs`), removing a large amount of duplicated code. No change to generated code or public API. ### v0.7.0 - 2026-04-25 - **[BREAKING]** Rename `derive_unsafe` to `derive_unchecked` (both the feature flag and the attribute). diff --git a/nutype_macros/src/common/generate/mod.rs b/nutype_macros/src/common/generate/mod.rs index 95142047..11a367e0 100644 --- a/nutype_macros/src/common/generate/mod.rs +++ b/nutype_macros/src/common/generate/mod.rs @@ -1,6 +1,7 @@ pub mod error; pub mod generics; pub mod new_unchecked; +pub mod numeric; pub mod parse_error; pub mod tests; pub mod traits; diff --git a/nutype_macros/src/common/generate/numeric.rs b/nutype_macros/src/common/generate/numeric.rs new file mode 100644 index 00000000..24462c7a --- /dev/null +++ b/nutype_macros/src/common/generate/numeric.rs @@ -0,0 +1,234 @@ +//! Shared code generation for numeric inner types (integer, float, decimal). +//! +//! Integer, float and decimal newtypes share almost all of their generated +//! code: the `__sanitize__` function, the `__validate__` function (bound checks +//! and predicate), and the validation error enum with its `Display` impl. The +//! only real differences are: +//! * float additionally supports the `Finite` validator; +//! * each kind allows a slightly different set of derivable traits (handled in +//! the per-kind `validate.rs`, not here). +//! +//! To avoid maintaining three near-identical copies, each kind implements +//! [`NumericValidatorTokens`] and [`NumericSanitizerTokens`] for its validator +//! and sanitizer enums, and the generation below is written once against those +//! traits. + +use proc_macro2::TokenStream; +use quote::{ToTokens, quote}; + +use crate::common::{ + generate::error::gen_impl_error_trait, + models::{ConstFn, ErrorTypePath, TypeName}, +}; + +/// A normalized, kind-agnostic view of a single numeric validator. +/// +/// Each numeric validator enum (`IntegerValidator`, `FloatValidator`, +/// `DecimalValidator`) maps onto these variants. The associated value of a +/// bound (or the predicate function) is exposed as `&dyn ToTokens`, which is all +/// the code generation needs. +pub enum NumericValidatorView<'a> { + Greater(&'a dyn ToTokens), + GreaterOrEqual(&'a dyn ToTokens), + Less(&'a dyn ToTokens), + LessOrEqual(&'a dyn ToTokens), + Predicate(&'a dyn ToTokens), + Finite, +} + +/// Implemented by every numeric validator enum so the shared generators can +/// treat them uniformly. +pub trait NumericValidatorTokens { + fn view(&self) -> NumericValidatorView<'_>; +} + +/// Implemented by every numeric sanitizer enum. Numeric sanitizers only ever +/// carry a custom `with = ...` function (plus a `_Phantom` variant that is +/// never constructed), so a single accessor is enough. +pub trait NumericSanitizerTokens { + /// Returns the custom sanitizer function, or `None` for the phantom variant. + fn custom_fn(&self) -> Option<&dyn ToTokens>; +} + +/// Generate the `__sanitize__` function shared by all numeric kinds. +pub fn gen_numeric_fn_sanitize( + inner_type: &IT, + sanitizers: &[S], + const_fn: ConstFn, +) -> TokenStream +where + S: NumericSanitizerTokens, + IT: ToTokens, +{ + let transformations: TokenStream = sanitizers + .iter() + .filter_map(|san| san.custom_fn()) + .map(|custom_sanitizer| { + quote!( + value = (#custom_sanitizer)(value); + ) + }) + .collect(); + + quote!( + #const_fn fn __sanitize__(mut value: #inner_type) -> #inner_type { + #transformations + value + } + ) +} + +/// Generate the `__validate__` function shared by all numeric kinds. +pub fn gen_numeric_fn_validate( + inner_type: &IT, + error_type_path: &ErrorTypePath, + validators: &[V], + const_fn: ConstFn, +) -> TokenStream +where + V: NumericValidatorTokens, + IT: ToTokens, +{ + let validations: TokenStream = validators + .iter() + .map(|validator| match validator.view() { + NumericValidatorView::Less(exclusive_upper_bound) => { + quote!( + if val >= #exclusive_upper_bound { + return Err(#error_type_path::LessViolated); + } + ) + } + NumericValidatorView::LessOrEqual(max) => { + quote!( + if val > #max { + return Err(#error_type_path::LessOrEqualViolated); + } + ) + } + NumericValidatorView::Greater(exclusive_lower_bound) => { + quote!( + if val <= #exclusive_lower_bound { + return Err(#error_type_path::GreaterViolated); + } + ) + } + NumericValidatorView::GreaterOrEqual(min) => { + quote!( + if val < #min { + return Err(#error_type_path::GreaterOrEqualViolated); + } + ) + } + NumericValidatorView::Predicate(custom_is_valid_fn) => { + quote!( + if !(#custom_is_valid_fn)(&val) { + return Err(#error_type_path::PredicateViolated); + } + ) + } + NumericValidatorView::Finite => { + quote!( + if !val.is_finite() { + return Err(#error_type_path::FiniteViolated); + } + ) + } + }) + .collect(); + + quote!( + #const_fn fn __validate__(val: &#inner_type) -> ::core::result::Result<(), #error_type_path> { + let val = *val; + #validations + Ok(()) + } + ) +} + +/// Generate the validation error enum (definition + `Display` + `Error`) shared +/// by all numeric kinds. +pub fn gen_numeric_validation_error_type( + type_name: &TypeName, + error_type_path: &ErrorTypePath, + validators: &[V], +) -> TokenStream +where + V: NumericValidatorTokens, +{ + let definition = gen_definition(error_type_path, validators); + let impl_display_trait = gen_impl_display_trait(type_name, error_type_path, validators); + let impl_error_trait = gen_impl_error_trait(error_type_path); + + quote! { + #[derive(Debug, Clone, PartialEq, Eq)] + #definition + + #impl_display_trait + #impl_error_trait + } +} + +fn gen_definition(error_type_path: &ErrorTypePath, validators: &[V]) -> TokenStream +where + V: NumericValidatorTokens, +{ + let error_variants: TokenStream = validators + .iter() + .map(|validator| match validator.view() { + NumericValidatorView::Greater(_) => quote!(GreaterViolated,), + NumericValidatorView::GreaterOrEqual(_) => quote!(GreaterOrEqualViolated,), + NumericValidatorView::Less(_) => quote!(LessViolated,), + NumericValidatorView::LessOrEqual(_) => quote!(LessOrEqualViolated,), + NumericValidatorView::Predicate(_) => quote!(PredicateViolated,), + NumericValidatorView::Finite => quote!(FiniteViolated,), + }) + .collect(); + + quote! { + #[allow(clippy::enum_variant_names)] + pub enum #error_type_path { + #error_variants + } + } +} + +fn gen_impl_display_trait( + type_name: &TypeName, + error_type_path: &ErrorTypePath, + validators: &[V], +) -> TokenStream +where + V: NumericValidatorTokens, +{ + let match_arms = validators.iter().map(|validator| match validator.view() { + NumericValidatorView::Greater(val) => quote! { + #error_type_path::GreaterViolated => write!(f, "{} is too small. The value must be greater than {:#?}.", stringify!(#type_name), #val) + }, + NumericValidatorView::GreaterOrEqual(val) => quote! { + #error_type_path::GreaterOrEqualViolated => write!(f, "{} is too small. The value must be greater or equal to {:#?}.", stringify!(#type_name), #val) + }, + NumericValidatorView::Less(val) => quote! { + #error_type_path::LessViolated => write!(f, "{} is too big. The value must be less than {:#?}.", stringify!(#type_name), #val) + }, + NumericValidatorView::LessOrEqual(val) => quote! { + #error_type_path::LessOrEqualViolated => write!(f, "{} is too big. The value must be less or equal to {:#?}.", stringify!(#type_name), #val) + }, + NumericValidatorView::Predicate(_) => quote! { + #error_type_path::PredicateViolated => write!(f, "{} failed the predicate test.", stringify!(#type_name)) + }, + NumericValidatorView::Finite => quote! { + #error_type_path::FiniteViolated => write!(f, "{} is not finite.", stringify!(#type_name)) + }, + }); + + quote! { + impl ::core::fmt::Display for #error_type_path { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + match self { + #(#match_arms,)* + } + } + } + } +} diff --git a/nutype_macros/src/common/models.rs b/nutype_macros/src/common/models.rs index 312cb194..85e39fd9 100644 --- a/nutype_macros/src/common/models.rs +++ b/nutype_macros/src/common/models.rs @@ -809,17 +809,21 @@ impl ToTokens for TypedCustomFunction { } } -/// This trait allows to reuse validation of numeric validators. -pub trait NumericBoundValidator { - fn greater(&self) -> Option; - fn greater_or_equal(&self) -> Option; - fn less(&self) -> Option; - fn less_or_equal(&self) -> Option; +/// This trait allows to reuse validation of numeric validators. The associated +/// `Bound` type is the inner value type of the bounds (e.g. `i32` for an integer +/// newtype), which is a function of the validator type itself. +pub trait NumericBoundValidator { + type Bound: Clone; + fn greater(&self) -> Option; + fn greater_or_equal(&self) -> Option; + fn less(&self) -> Option; + fn less_or_equal(&self) -> Option; } macro_rules! impl_numeric_bound_validator { ($tp:ident) => { - impl crate::common::models::NumericBoundValidator for $tp { + impl crate::common::models::NumericBoundValidator for $tp { + type Bound = T; fn greater(&self) -> Option { if let $tp::Greater(ValueOrExpr::Value(value)) = self { Some(value.clone()) @@ -919,3 +923,43 @@ macro_rules! impl_numeric_bound_on_vec_of { } pub(crate) use impl_numeric_bound_on_vec_of; + +/// Define the inner-type enum for a numeric kind (integer, float) together with +/// its marker-trait impls, `ToTokens` and `Display`. Integer and float share +/// the exact same shape, differing only in the enum name, the marker trait and +/// the list of concrete types. +macro_rules! define_numeric_inner_type { + ($enum_name:ident, $marker_trait:ident, $($tp:ty => $variant:ident),* $(,)?) => { + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub enum $enum_name { + $($variant),* + } + + $( + impl $marker_trait for $tp {} + )* + + impl ::quote::ToTokens for $enum_name { + fn to_tokens(&self, token_stream: &mut ::proc_macro2::TokenStream) { + let type_stream = match self { + $( + Self::$variant => ::quote::quote!($tp), + )* + }; + ::quote::ToTokens::to_tokens(&type_stream, token_stream); + } + } + + impl ::core::fmt::Display for $enum_name { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + match self { + $( + Self::$variant => stringify!($tp).fmt(f), + )* + } + } + } + }; +} + +pub(crate) use define_numeric_inner_type; diff --git a/nutype_macros/src/common/validate.rs b/nutype_macros/src/common/validate.rs index 9176938e..d6095feb 100644 --- a/nutype_macros/src/common/validate.rs +++ b/nutype_macros/src/common/validate.rs @@ -103,10 +103,10 @@ macro_rules! find_bound_variant { }; } -pub fn validate_numeric_bounds(validators: &[SpannedItem]) -> Result<(), syn::Error> +pub fn validate_numeric_bounds(validators: &[SpannedItem]) -> Result<(), syn::Error> where - V: NumericBoundValidator, - T: Clone + PartialOrd, + V: NumericBoundValidator, + V::Bound: Clone + PartialOrd, { let maybe_greater = find_bound_variant!(validators, greater); let maybe_greater_or_equal = find_bound_variant!(validators, greater_or_equal); @@ -155,6 +155,62 @@ where Ok(()) } +/// Validate the validators of a numeric newtype (integer, float, decimal): +/// reject duplicates and check the lower/upper bound relationship. +pub fn validate_numeric_validators(validators: Vec>) -> Result, syn::Error> +where + V: NumericBoundValidator + Kinded, + ::Kind: core::fmt::Display, + V::Bound: Clone + PartialOrd, +{ + validate_duplicates(&validators, |kind| { + format!( + "Duplicated validator `{kind}`.\nYou're a great engineer, but don't forget to take care of yourself!" + ) + })?; + + validate_numeric_bounds(&validators)?; + + let validators: Vec<_> = validators.into_iter().map(|v| v.item).collect(); + Ok(validators) +} + +/// Validate the sanitizers of a numeric newtype (integer, float, decimal): +/// reject duplicates. +pub fn validate_numeric_sanitizers(sanitizers: Vec>) -> Result, syn::Error> +where + S: Kinded, + ::Kind: core::fmt::Display, +{ + validate_duplicates(&sanitizers, |kind| { + format!("Duplicated sanitizer `{kind}`.\nIt happens, don't worry. We still love you!") + })?; + + let sanitizers: Vec<_> = sanitizers.into_iter().map(|s| s.item).collect(); + Ok(sanitizers) +} + +/// Validate the full guard (sanitizers + validators) of a numeric newtype. +/// Shared by integer, float and decimal. +pub fn validate_numeric_guard( + raw_guard: RawGuard, SpannedItem>, + type_name: &TypeName, +) -> Result, syn::Error> +where + S: Kinded, + ::Kind: core::fmt::Display, + V: NumericBoundValidator + Kinded, + ::Kind: core::fmt::Display, + V::Bound: Clone + PartialOrd, +{ + validate_guard( + raw_guard, + type_name, + validate_numeric_validators, + validate_numeric_sanitizers, + ) +} + pub fn validate_traits_from_xor_try_from( spanned_derive_traits: &[SpannedDeriveTrait], ) -> Result<(), syn::Error> { diff --git a/nutype_macros/src/decimal/generate/error.rs b/nutype_macros/src/decimal/generate/error.rs deleted file mode 100644 index 56ab22b4..00000000 --- a/nutype_macros/src/decimal/generate/error.rs +++ /dev/null @@ -1,93 +0,0 @@ -use proc_macro2::TokenStream; -use quote::{ToTokens, quote}; - -use super::super::models::DecimalValidator; -use crate::common::{ - generate::error::gen_impl_error_trait, - models::{ErrorTypePath, TypeName}, -}; - -pub fn gen_validation_error_type( - type_name: &TypeName, - error_type_path: &ErrorTypePath, - validators: &[DecimalValidator], -) -> TokenStream { - let definition = gen_definition(error_type_path, validators); - let impl_display_trait = gen_impl_display_trait(type_name, error_type_path, validators); - let impl_error_trait = gen_impl_error_trait(error_type_path); - - quote! { - #[derive(Debug, Clone, PartialEq, Eq)] - #definition - - #impl_display_trait - #impl_error_trait - } -} - -fn gen_definition( - error_type_path: &ErrorTypePath, - validators: &[DecimalValidator], -) -> TokenStream { - let error_variants: TokenStream = validators - .iter() - .map(|validator| match validator { - DecimalValidator::Greater(_) => { - quote!(GreaterViolated,) - } - DecimalValidator::GreaterOrEqual(_) => { - quote!(GreaterOrEqualViolated,) - } - DecimalValidator::Less(_) => { - quote!(LessViolated,) - } - DecimalValidator::LessOrEqual(_) => { - quote!(LessOrEqualViolated,) - } - DecimalValidator::Predicate(_) => { - quote!(PredicateViolated,) - } - }) - .collect(); - - quote! { - #[allow(clippy::enum_variant_names)] - pub enum #error_type_path { - #error_variants - } - } -} - -fn gen_impl_display_trait( - type_name: &TypeName, - error_type_path: &ErrorTypePath, - validators: &[DecimalValidator], -) -> TokenStream { - let match_arms = validators.iter().map(|validator| match validator { - DecimalValidator::Greater(val) => quote! { - #error_type_path::GreaterViolated => write!(f, "{} is too small. The value must be greater than {:#?}.", stringify!(#type_name), #val) - }, - DecimalValidator::GreaterOrEqual(val) => quote! { - #error_type_path::GreaterOrEqualViolated => write!(f, "{} is too small. The value must be greater or equal to {:#?}.", stringify!(#type_name), #val) - }, - DecimalValidator::Less(val) => quote! { - #error_type_path::LessViolated=> write!(f, "{} is too big. The value must be less than {:#?}.", stringify!(#type_name), #val) - }, - DecimalValidator::LessOrEqual(val) => quote! { - #error_type_path::LessOrEqualViolated=> write!(f, "{} is too big. The value must be less or equal to {:#?}.", stringify!(#type_name), #val) - }, - DecimalValidator::Predicate(_) => quote! { - #error_type_path::PredicateViolated => write!(f, "{} failed the predicate test.", stringify!(#type_name)) - }, - }); - - quote! { - impl ::core::fmt::Display for #error_type_path { - fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - match self { - #(#match_arms,)* - } - } - } - } -} diff --git a/nutype_macros/src/decimal/generate/mod.rs b/nutype_macros/src/decimal/generate/mod.rs index 428a12e4..5dbc7580 100644 --- a/nutype_macros/src/decimal/generate/mod.rs +++ b/nutype_macros/src/decimal/generate/mod.rs @@ -1,4 +1,3 @@ -pub mod error; pub mod traits; use std::collections::HashSet; @@ -7,7 +6,7 @@ use proc_macro2::TokenStream; use quote::{ToTokens, quote}; use syn::Generics; -use self::{error::gen_validation_error_type, traits::gen_traits}; +use self::traits::gen_traits; use super::{ DecimalNewtype, models::{ @@ -18,6 +17,9 @@ use super::{ use crate::common::{ generate::{ GenerateNewtype, + numeric::{ + gen_numeric_fn_sanitize, gen_numeric_fn_validate, gen_numeric_validation_error_type, + }, tests::{ gen_test_should_have_consistent_lower_and_upper_boundaries, gen_test_should_have_valid_default_value, @@ -43,26 +45,7 @@ where sanitizers: &[Self::Sanitizer], const_fn: ConstFn, ) -> TokenStream { - let transformations: TokenStream = sanitizers - .iter() - .map(|san| match san { - DecimalSanitizer::With(custom_sanitizer) => { - quote!( - value = (#custom_sanitizer)(value); - ) - } - DecimalSanitizer::_Phantom(_) => { - unreachable!("decimal::gen: DecimalSanitizer::_Phantom must not be used") - } - }) - .collect(); - - quote!( - #const_fn fn __sanitize__(mut value: #inner_type) -> #inner_type { - #transformations - value - } - ) + gen_numeric_fn_sanitize(inner_type, sanitizers, const_fn) } fn gen_fn_validate( @@ -71,54 +54,7 @@ where validators: &[Self::Validator], const_fn: ConstFn, ) -> TokenStream { - let validations: TokenStream = validators - .iter() - .map(|validator| match validator { - DecimalValidator::Less(exclusive_upper_bound) => { - quote!( - if val >= #exclusive_upper_bound { - return Err(#error_type_path::LessViolated); - } - ) - } - DecimalValidator::LessOrEqual(max) => { - quote!( - if val > #max { - return Err(#error_type_path::LessOrEqualViolated); - } - ) - } - DecimalValidator::Greater(exclusive_lower_bound) => { - quote!( - if val <= #exclusive_lower_bound { - return Err(#error_type_path::GreaterViolated); - } - ) - } - DecimalValidator::GreaterOrEqual(min) => { - quote!( - if val < #min { - return Err(#error_type_path::GreaterOrEqualViolated); - } - ) - } - DecimalValidator::Predicate(custom_is_valid_fn) => { - quote!( - if !(#custom_is_valid_fn)(&val) { - return Err(#error_type_path::PredicateViolated); - } - ) - } - }) - .collect(); - - quote!( - #const_fn fn __validate__(val: &#inner_type) -> ::core::result::Result<(), #error_type_path> { - let val = *val; - #validations - Ok(()) - } - ) + gen_numeric_fn_validate(inner_type, error_type_path, validators, const_fn) } fn gen_validation_error_type( @@ -126,7 +62,7 @@ where error_type_path: &ErrorTypePath, validators: &[Self::Validator], ) -> TokenStream { - gen_validation_error_type(type_name, error_type_path, validators) + gen_numeric_validation_error_type(type_name, error_type_path, validators) } fn gen_traits( diff --git a/nutype_macros/src/decimal/models.rs b/nutype_macros/src/decimal/models.rs index d5e8f546..a0743962 100644 --- a/nutype_macros/src/decimal/models.rs +++ b/nutype_macros/src/decimal/models.rs @@ -4,9 +4,12 @@ use kinded::Kinded; use proc_macro2::TokenStream; use quote::{ToTokens, quote}; -use crate::common::models::{ - Guard, RawGuard, SpannedItem, TypeTrait, TypedCustomFunction, ValueOrExpr, - impl_numeric_bound_on_vec_of, impl_numeric_bound_validator, +use crate::common::{ + generate::numeric::{NumericSanitizerTokens, NumericValidatorTokens, NumericValidatorView}, + models::{ + Guard, RawGuard, SpannedItem, TypeTrait, TypedCustomFunction, ValueOrExpr, + impl_numeric_bound_on_vec_of, impl_numeric_bound_validator, + }, }; // Literal value @@ -120,6 +123,27 @@ pub enum DecimalValidator { impl_numeric_bound_validator!(DecimalValidator); impl_numeric_bound_on_vec_of!(DecimalValidator); +impl NumericValidatorTokens for DecimalValidator { + fn view(&self) -> NumericValidatorView<'_> { + match self { + DecimalValidator::Greater(v) => NumericValidatorView::Greater(v), + DecimalValidator::GreaterOrEqual(v) => NumericValidatorView::GreaterOrEqual(v), + DecimalValidator::Less(v) => NumericValidatorView::Less(v), + DecimalValidator::LessOrEqual(v) => NumericValidatorView::LessOrEqual(v), + DecimalValidator::Predicate(f) => NumericValidatorView::Predicate(f), + } + } +} + +impl NumericSanitizerTokens for DecimalSanitizer { + fn custom_fn(&self) -> Option<&dyn ToTokens> { + match self { + DecimalSanitizer::With(f) => Some(f), + DecimalSanitizer::_Phantom(_) => None, + } + } +} + pub type SpannedDecimalValidator = SpannedItem>; // Traits diff --git a/nutype_macros/src/decimal/validate.rs b/nutype_macros/src/decimal/validate.rs index 1ca15a1d..cb077fd7 100644 --- a/nutype_macros/src/decimal/validate.rs +++ b/nutype_macros/src/decimal/validate.rs @@ -2,15 +2,10 @@ use proc_macro2::Span; use crate::common::{ models::{CfgAttrEntry, DeriveTrait, SpannedDeriveTrait, TypeName, ValidatedDerives}, - validate::{ - validate_all_derive_traits, validate_duplicates, validate_guard, validate_numeric_bounds, - }, + validate::{validate_all_derive_traits, validate_numeric_guard}, }; -use super::models::{ - DecimalDeriveTrait, DecimalGuard, DecimalRawGuard, DecimalSanitizer, DecimalValidator, - SpannedDecimalSanitizer, SpannedDecimalValidator, -}; +use super::models::{DecimalDeriveTrait, DecimalGuard, DecimalRawGuard}; pub fn validate_decimal_guard( raw_guard: DecimalRawGuard, @@ -19,44 +14,7 @@ pub fn validate_decimal_guard( where T: PartialOrd + Clone, { - validate_guard( - raw_guard, - type_name, - validate_validators, - validate_sanitizers, - ) -} - -fn validate_validators( - validators: Vec>, -) -> Result>, syn::Error> -where - T: PartialOrd + Clone, -{ - validate_duplicates(&validators, |kind| { - format!( - "Duplicated validator `{kind}`.\nYou're a great engineer, but don't forget to take care of yourself!" - ) - })?; - - validate_numeric_bounds(&validators)?; - - let validators: Vec<_> = validators.into_iter().map(|v| v.item).collect(); - Ok(validators) -} - -fn validate_sanitizers( - sanitizers: Vec>, -) -> Result>, syn::Error> -where - T: PartialOrd + Clone, -{ - validate_duplicates(&sanitizers, |kind| { - format!("Duplicated sanitizer `{kind}`.\nIt happens, don't worry. We still love you!") - })?; - - let sanitizers: Vec<_> = sanitizers.into_iter().map(|s| s.item).collect(); - Ok(sanitizers) + validate_numeric_guard(raw_guard, type_name) } pub fn validate_decimal_derive_traits( diff --git a/nutype_macros/src/float/generate/error.rs b/nutype_macros/src/float/generate/error.rs deleted file mode 100644 index f41ea5d6..00000000 --- a/nutype_macros/src/float/generate/error.rs +++ /dev/null @@ -1,100 +0,0 @@ -use proc_macro2::TokenStream; -use quote::{ToTokens, quote}; - -use crate::common::{ - generate::error::gen_impl_error_trait, - models::{ErrorTypePath, TypeName}, -}; - -use super::super::models::FloatValidator; - -pub fn gen_validation_error_type( - type_name: &TypeName, - error_type_path: &ErrorTypePath, - validators: &[FloatValidator], -) -> TokenStream { - let definition = gen_definition(error_type_path, validators); - let impl_display_trait = gen_impl_display_trait(type_name, error_type_path, validators); - let impl_error_trait = gen_impl_error_trait(error_type_path); - - quote! { - #[derive(Debug, Clone, PartialEq, Eq)] - #definition - - #impl_display_trait - #impl_error_trait - } -} - -fn gen_definition( - error_type_path: &ErrorTypePath, - validators: &[FloatValidator], -) -> TokenStream { - let error_variants: TokenStream = validators - .iter() - .map(|validator| match validator { - FloatValidator::Greater(_) => { - quote!(GreaterViolated,) - } - FloatValidator::GreaterOrEqual(_) => { - quote!(GreaterOrEqualViolated,) - } - FloatValidator::LessOrEqual(_) => { - quote!(LessOrEqualViolated,) - } - FloatValidator::Less(_) => { - quote!(LessViolated,) - } - FloatValidator::Predicate(_) => { - quote!(PredicateViolated,) - } - FloatValidator::Finite => { - quote!(FiniteViolated,) - } - }) - .collect(); - - quote! { - #[allow(clippy::enum_variant_names)] - pub enum #error_type_path { - #error_variants - } - } -} - -fn gen_impl_display_trait( - type_name: &TypeName, - error_type_path: &ErrorTypePath, - validators: &[FloatValidator], -) -> TokenStream { - let match_arms = validators.iter().map(|validator| match validator { - FloatValidator::Greater(val) => quote! { - #error_type_path::GreaterViolated => write!(f, "{} is too small. The value must be greater than {:#?}.", stringify!(#type_name), #val) - }, - FloatValidator::GreaterOrEqual(val) => quote! { - #error_type_path::GreaterOrEqualViolated => write!(f, "{} is too small. The value must be greater or equal to {:#?}.", stringify!(#type_name), #val) - }, - FloatValidator::LessOrEqual(val) => quote! { - #error_type_path::LessOrEqualViolated=> write!(f, "{} is too big. The value must be less than {:#?}.", stringify!(#type_name), #val) - }, - FloatValidator::Less(val) => quote! { - #error_type_path::LessViolated=> write!(f, "{} is too big. The value must be less or equal to {:#?}.", stringify!(#type_name), #val) - }, - FloatValidator::Predicate(_) => quote! { - #error_type_path::PredicateViolated => write!(f, "{} failed the predicate test.", stringify!(#type_name)) - }, - FloatValidator::Finite => quote! { - #error_type_path::FiniteViolated => write!(f, "{} is not finite.", stringify!(#type_name)) - }, - }); - - quote! { - impl ::core::fmt::Display for #error_type_path { - fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - match self { - #(#match_arms,)* - } - } - } - } -} diff --git a/nutype_macros/src/float/generate/mod.rs b/nutype_macros/src/float/generate/mod.rs index 23f5a0d9..1af56a04 100644 --- a/nutype_macros/src/float/generate/mod.rs +++ b/nutype_macros/src/float/generate/mod.rs @@ -1,4 +1,3 @@ -pub mod error; pub mod traits; use std::collections::HashSet; @@ -7,7 +6,6 @@ use proc_macro2::TokenStream; use quote::{ToTokens, quote}; use syn::Generics; -use self::error::gen_validation_error_type; use super::{ FloatNewtype, models::{FloatDeriveTrait, FloatGuard, FloatSanitizer, FloatType, FloatValidator}, @@ -16,6 +14,9 @@ use crate::{ common::{ generate::{ GenerateNewtype, + numeric::{ + gen_numeric_fn_sanitize, gen_numeric_fn_validate, gen_numeric_validation_error_type, + }, tests::{ gen_test_should_have_consistent_lower_and_upper_boundaries, gen_test_should_have_valid_default_value, @@ -45,26 +46,7 @@ where sanitizers: &[Self::Sanitizer], const_fn: ConstFn, ) -> TokenStream { - let transformations: TokenStream = sanitizers - .iter() - .map(|san| match san { - FloatSanitizer::With(custom_sanitizer) => { - quote!( - value = (#custom_sanitizer)(value); - ) - } - FloatSanitizer::_Phantom(_) => { - unreachable!("float::gen FloatSanitizer::_Phantom must not be used") - } - }) - .collect(); - - quote!( - #const_fn fn __sanitize__(mut value: #inner_type) -> #inner_type { - #transformations - value - } - ) + gen_numeric_fn_sanitize(inner_type, sanitizers, const_fn) } fn gen_fn_validate( @@ -73,61 +55,7 @@ where validators: &[Self::Validator], const_fn: ConstFn, ) -> TokenStream { - let validations: TokenStream = validators - .iter() - .map(|validator| match validator { - FloatValidator::Less(exclusive_upper_bound) => { - quote!( - if val >= #exclusive_upper_bound { - return Err(#error_type_path::LessViolated); - } - ) - } - FloatValidator::LessOrEqual(max) => { - quote!( - if val > #max { - return Err(#error_type_path::LessOrEqualViolated); - } - ) - } - FloatValidator::Greater(exclusive_lower_bound) => { - quote!( - if val <= #exclusive_lower_bound { - return Err(#error_type_path::GreaterViolated); - } - ) - } - FloatValidator::GreaterOrEqual(min) => { - quote!( - if val < #min { - return Err(#error_type_path::GreaterOrEqualViolated); - } - ) - } - FloatValidator::Predicate(custom_is_valid_fn) => { - quote!( - if !(#custom_is_valid_fn)(&val) { - return Err(#error_type_path::PredicateViolated); - } - ) - } - FloatValidator::Finite => { - quote!( - if !val.is_finite() { - return Err(#error_type_path::FiniteViolated); - } - ) - } - }) - .collect(); - - quote!( - #const_fn fn __validate__(val: &#inner_type) -> core::result::Result<(), #error_type_path> { - let val = *val; - #validations - Ok(()) - } - ) + gen_numeric_fn_validate(inner_type, error_type_path, validators, const_fn) } fn gen_validation_error_type( @@ -135,7 +63,7 @@ where error_type_path: &ErrorTypePath, validators: &[Self::Validator], ) -> TokenStream { - gen_validation_error_type(type_name, error_type_path, validators) + gen_numeric_validation_error_type(type_name, error_type_path, validators) } fn gen_traits( diff --git a/nutype_macros/src/float/models.rs b/nutype_macros/src/float/models.rs index 774ff1af..57537f62 100644 --- a/nutype_macros/src/float/models.rs +++ b/nutype_macros/src/float/models.rs @@ -1,9 +1,13 @@ use kinded::Kinded; use proc_macro2::TokenStream; - -use crate::common::models::{ - Guard, RawGuard, SpannedItem, TypeTrait, TypedCustomFunction, ValueOrExpr, - impl_numeric_bound_on_vec_of, impl_numeric_bound_validator, +use quote::ToTokens; + +use crate::common::{ + generate::numeric::{NumericSanitizerTokens, NumericValidatorTokens, NumericValidatorView}, + models::{ + Guard, RawGuard, SpannedItem, TypeTrait, TypedCustomFunction, ValueOrExpr, + define_numeric_inner_type, impl_numeric_bound_on_vec_of, impl_numeric_bound_validator, + }, }; // Sanitizer @@ -35,6 +39,28 @@ pub enum FloatValidator { impl_numeric_bound_validator!(FloatValidator); impl_numeric_bound_on_vec_of!(FloatValidator); +impl NumericValidatorTokens for FloatValidator { + fn view(&self) -> NumericValidatorView<'_> { + match self { + FloatValidator::Greater(v) => NumericValidatorView::Greater(v), + FloatValidator::GreaterOrEqual(v) => NumericValidatorView::GreaterOrEqual(v), + FloatValidator::Less(v) => NumericValidatorView::Less(v), + FloatValidator::LessOrEqual(v) => NumericValidatorView::LessOrEqual(v), + FloatValidator::Predicate(f) => NumericValidatorView::Predicate(f), + FloatValidator::Finite => NumericValidatorView::Finite, + } + } +} + +impl NumericSanitizerTokens for FloatSanitizer { + fn custom_fn(&self) -> Option<&dyn ToTokens> { + match self { + FloatSanitizer::With(f) => Some(f), + FloatSanitizer::_Phantom(_) => None, + } + } +} + pub type SpannedFloatValidator = SpannedItem>; // Traits @@ -81,42 +107,8 @@ pub type FloatGuard = Guard, FloatValidator>; pub trait FloatType {} -macro_rules! define_float_inner_type { - ($($tp:ty => $variant:ident),*) => { - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - pub enum FloatInnerType { - $($variant),* - } - - $( - impl FloatType for $tp { - } - )* - - impl quote::ToTokens for FloatInnerType { - fn to_tokens(&self, token_stream: &mut TokenStream) { - let type_stream = match self { - $( - Self::$variant => quote::quote!($tp), - )* - }; - type_stream.to_tokens(token_stream); - } - } - - impl ::core::fmt::Display for FloatInnerType { - fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - match self { - $( - Self::$variant => stringify!($tp).fmt(f), - )* - } - } - } - } -} - -define_float_inner_type!( +define_numeric_inner_type!( + FloatInnerType, FloatType, f32 => F32, f64 => F64 ); diff --git a/nutype_macros/src/float/validate.rs b/nutype_macros/src/float/validate.rs index 043c5b13..b0f3b70c 100644 --- a/nutype_macros/src/float/validate.rs +++ b/nutype_macros/src/float/validate.rs @@ -6,15 +6,10 @@ use crate::common::{ CfgAttrContent, CfgAttrEntry, DeriveTrait, SpannedDeriveTrait, TypeName, ValidatedDerives, Validation, }, - validate::{ - validate_all_derive_traits, validate_duplicates, validate_guard, validate_numeric_bounds, - }, + validate::{validate_all_derive_traits, validate_numeric_guard}, }; -use super::models::{ - FloatDeriveTrait, FloatGuard, FloatRawGuard, FloatSanitizer, FloatValidator, - FloatValidatorKind, SpannedFloatSanitizer, SpannedFloatValidator, -}; +use super::models::{FloatDeriveTrait, FloatGuard, FloatRawGuard, FloatValidatorKind}; pub fn validate_float_guard( raw_guard: FloatRawGuard, @@ -23,44 +18,7 @@ pub fn validate_float_guard( where T: PartialOrd + Clone, { - validate_guard( - raw_guard, - type_name, - validate_validators, - validate_sanitizers, - ) -} - -fn validate_validators( - validators: Vec>, -) -> Result>, syn::Error> -where - T: PartialOrd + Clone, -{ - validate_duplicates(&validators, |kind| { - format!( - "Duplicated validator `{kind}`.\nYou're a great engineer, but don't forget to take care of yourself!" - ) - })?; - - validate_numeric_bounds(&validators)?; - - let validators: Vec<_> = validators.into_iter().map(|v| v.item).collect(); - Ok(validators) -} - -fn validate_sanitizers( - sanitizers: Vec>, -) -> Result>, syn::Error> -where - T: PartialOrd + Clone, -{ - validate_duplicates(&sanitizers, |kind| { - format!("Duplicated sanitizer `{kind}`.\nIt happens, don't worry. We still love you!") - })?; - - let sanitizers: Vec<_> = sanitizers.into_iter().map(|s| s.item).collect(); - Ok(sanitizers) + validate_numeric_guard(raw_guard, type_name) } fn has_validation_against_nan(guard: &FloatGuard) -> bool { diff --git a/nutype_macros/src/integer/generate/error.rs b/nutype_macros/src/integer/generate/error.rs deleted file mode 100644 index 2eb56e92..00000000 --- a/nutype_macros/src/integer/generate/error.rs +++ /dev/null @@ -1,93 +0,0 @@ -use proc_macro2::TokenStream; -use quote::{ToTokens, quote}; - -use super::super::models::IntegerValidator; -use crate::common::{ - generate::error::gen_impl_error_trait, - models::{ErrorTypePath, TypeName}, -}; - -pub fn gen_validation_error_type( - type_name: &TypeName, - error_type_path: &ErrorTypePath, - validators: &[IntegerValidator], -) -> TokenStream { - let definition = gen_definition(error_type_path, validators); - let impl_display_trait = gen_impl_display_trait(type_name, error_type_path, validators); - let impl_error_trait = gen_impl_error_trait(error_type_path); - - quote! { - #[derive(Debug, Clone, PartialEq, Eq)] - #definition - - #impl_display_trait - #impl_error_trait - } -} - -fn gen_definition( - error_type_path: &ErrorTypePath, - validators: &[IntegerValidator], -) -> TokenStream { - let error_variants: TokenStream = validators - .iter() - .map(|validator| match validator { - IntegerValidator::Greater(_) => { - quote!(GreaterViolated,) - } - IntegerValidator::GreaterOrEqual(_) => { - quote!(GreaterOrEqualViolated,) - } - IntegerValidator::Less(_) => { - quote!(LessViolated,) - } - IntegerValidator::LessOrEqual(_) => { - quote!(LessOrEqualViolated,) - } - IntegerValidator::Predicate(_) => { - quote!(PredicateViolated,) - } - }) - .collect(); - - quote! { - #[allow(clippy::enum_variant_names)] - pub enum #error_type_path { - #error_variants - } - } -} - -fn gen_impl_display_trait( - type_name: &TypeName, - error_type_path: &ErrorTypePath, - validators: &[IntegerValidator], -) -> TokenStream { - let match_arms = validators.iter().map(|validator| match validator { - IntegerValidator::Greater(val) => quote! { - #error_type_path::GreaterViolated => write!(f, "{} is too small. The value must be greater than {:#?}.", stringify!(#type_name), #val) - }, - IntegerValidator::GreaterOrEqual(val) => quote! { - #error_type_path::GreaterOrEqualViolated => write!(f, "{} is too small. The value must be greater or equal to {:#?}.", stringify!(#type_name), #val) - }, - IntegerValidator::Less(val) => quote! { - #error_type_path::LessViolated=> write!(f, "{} is too big. The value must be less than {:#?}.", stringify!(#type_name), #val) - }, - IntegerValidator::LessOrEqual(val) => quote! { - #error_type_path::LessOrEqualViolated=> write!(f, "{} is too big. The value must be less or equal to {:#?}.", stringify!(#type_name), #val) - }, - IntegerValidator::Predicate(_) => quote! { - #error_type_path::PredicateViolated => write!(f, "{} failed the predicate test.", stringify!(#type_name)) - }, - }); - - quote! { - impl ::core::fmt::Display for #error_type_path { - fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - match self { - #(#match_arms,)* - } - } - } - } -} diff --git a/nutype_macros/src/integer/generate/mod.rs b/nutype_macros/src/integer/generate/mod.rs index d568415f..4999499b 100644 --- a/nutype_macros/src/integer/generate/mod.rs +++ b/nutype_macros/src/integer/generate/mod.rs @@ -1,4 +1,3 @@ -pub mod error; pub mod traits; use std::collections::HashSet; @@ -7,7 +6,7 @@ use proc_macro2::TokenStream; use quote::{ToTokens, quote}; use syn::Generics; -use self::{error::gen_validation_error_type, traits::gen_traits}; +use self::traits::gen_traits; use super::{ IntegerNewtype, models::{ @@ -18,6 +17,9 @@ use super::{ use crate::common::{ generate::{ GenerateNewtype, + numeric::{ + gen_numeric_fn_sanitize, gen_numeric_fn_validate, gen_numeric_validation_error_type, + }, tests::{ gen_test_should_have_consistent_lower_and_upper_boundaries, gen_test_should_have_valid_default_value, @@ -43,26 +45,7 @@ where sanitizers: &[Self::Sanitizer], const_fn: ConstFn, ) -> TokenStream { - let transformations: TokenStream = sanitizers - .iter() - .map(|san| match san { - IntegerSanitizer::With(custom_sanitizer) => { - quote!( - value = (#custom_sanitizer)(value); - ) - } - IntegerSanitizer::_Phantom(_) => { - unreachable!("integer::gen: IntegerSanitizer::_Phantom must not be used") - } - }) - .collect(); - - quote!( - #const_fn fn __sanitize__(mut value: #inner_type) -> #inner_type { - #transformations - value - } - ) + gen_numeric_fn_sanitize(inner_type, sanitizers, const_fn) } fn gen_fn_validate( @@ -71,54 +54,7 @@ where validators: &[Self::Validator], const_fn: ConstFn, ) -> TokenStream { - let validations: TokenStream = validators - .iter() - .map(|validator| match validator { - IntegerValidator::Less(exclusive_upper_bound) => { - quote!( - if val >= #exclusive_upper_bound { - return Err(#error_type_path::LessViolated); - } - ) - } - IntegerValidator::LessOrEqual(max) => { - quote!( - if val > #max { - return Err(#error_type_path::LessOrEqualViolated); - } - ) - } - IntegerValidator::Greater(exclusive_lower_bound) => { - quote!( - if val <= #exclusive_lower_bound { - return Err(#error_type_path::GreaterViolated); - } - ) - } - IntegerValidator::GreaterOrEqual(min) => { - quote!( - if val < #min { - return Err(#error_type_path::GreaterOrEqualViolated); - } - ) - } - IntegerValidator::Predicate(custom_is_valid_fn) => { - quote!( - if !(#custom_is_valid_fn)(&val) { - return Err(#error_type_path::PredicateViolated); - } - ) - } - }) - .collect(); - - quote!( - #const_fn fn __validate__(val: &#inner_type) -> ::core::result::Result<(), #error_type_path> { - let val = *val; - #validations - Ok(()) - } - ) + gen_numeric_fn_validate(inner_type, error_type_path, validators, const_fn) } fn gen_validation_error_type( @@ -126,7 +62,7 @@ where error_type_path: &ErrorTypePath, validators: &[Self::Validator], ) -> TokenStream { - gen_validation_error_type(type_name, error_type_path, validators) + gen_numeric_validation_error_type(type_name, error_type_path, validators) } fn gen_traits( diff --git a/nutype_macros/src/integer/models.rs b/nutype_macros/src/integer/models.rs index 99f5bd29..6b4b5f0e 100644 --- a/nutype_macros/src/integer/models.rs +++ b/nutype_macros/src/integer/models.rs @@ -1,9 +1,13 @@ use kinded::Kinded; use proc_macro2::TokenStream; - -use crate::common::models::{ - Guard, RawGuard, SpannedItem, TypeTrait, TypedCustomFunction, ValueOrExpr, - impl_numeric_bound_on_vec_of, impl_numeric_bound_validator, +use quote::ToTokens; + +use crate::common::{ + generate::numeric::{NumericSanitizerTokens, NumericValidatorTokens, NumericValidatorView}, + models::{ + Guard, RawGuard, SpannedItem, TypeTrait, TypedCustomFunction, ValueOrExpr, + define_numeric_inner_type, impl_numeric_bound_on_vec_of, impl_numeric_bound_validator, + }, }; // Sanitizer @@ -34,6 +38,27 @@ pub enum IntegerValidator { impl_numeric_bound_validator!(IntegerValidator); impl_numeric_bound_on_vec_of!(IntegerValidator); +impl NumericValidatorTokens for IntegerValidator { + fn view(&self) -> NumericValidatorView<'_> { + match self { + IntegerValidator::Greater(v) => NumericValidatorView::Greater(v), + IntegerValidator::GreaterOrEqual(v) => NumericValidatorView::GreaterOrEqual(v), + IntegerValidator::Less(v) => NumericValidatorView::Less(v), + IntegerValidator::LessOrEqual(v) => NumericValidatorView::LessOrEqual(v), + IntegerValidator::Predicate(f) => NumericValidatorView::Predicate(f), + } + } +} + +impl NumericSanitizerTokens for IntegerSanitizer { + fn custom_fn(&self) -> Option<&dyn ToTokens> { + match self { + IntegerSanitizer::With(f) => Some(f), + IntegerSanitizer::_Phantom(_) => None, + } + } +} + pub type SpannedIntegerValidator = SpannedItem>; // Traits @@ -81,42 +106,8 @@ pub type IntegerGuard = Guard, IntegerValidator>; pub trait IntegerType {} -macro_rules! define_integer_inner_type { - ($($tp:ty => $variant:ident),*) => { - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - pub enum IntegerInnerType { - $($variant),* - } - - $( - impl IntegerType for $tp { - } - )* - - impl quote::ToTokens for IntegerInnerType { - fn to_tokens(&self, token_stream: &mut TokenStream) { - let type_stream = match self { - $( - Self::$variant => quote::quote!($tp), - )* - }; - type_stream.to_tokens(token_stream); - } - } - - impl ::core::fmt::Display for IntegerInnerType { - fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - match self { - $( - Self::$variant => stringify!($tp).fmt(f), - )* - } - } - } - } -} - -define_integer_inner_type!( +define_numeric_inner_type!( + IntegerInnerType, IntegerType, u8 => U8, u16 => U16, u32 => U32, diff --git a/nutype_macros/src/integer/validate.rs b/nutype_macros/src/integer/validate.rs index 65541735..10b19c92 100644 --- a/nutype_macros/src/integer/validate.rs +++ b/nutype_macros/src/integer/validate.rs @@ -2,15 +2,10 @@ use proc_macro2::Span; use crate::common::{ models::{CfgAttrEntry, DeriveTrait, SpannedDeriveTrait, TypeName, ValidatedDerives}, - validate::{ - validate_all_derive_traits, validate_duplicates, validate_guard, validate_numeric_bounds, - }, + validate::{validate_all_derive_traits, validate_numeric_guard}, }; -use super::models::{ - IntegerDeriveTrait, IntegerGuard, IntegerRawGuard, IntegerSanitizer, IntegerValidator, - SpannedIntegerSanitizer, SpannedIntegerValidator, -}; +use super::models::{IntegerDeriveTrait, IntegerGuard, IntegerRawGuard}; pub fn validate_integer_guard( raw_guard: IntegerRawGuard, @@ -19,44 +14,7 @@ pub fn validate_integer_guard( where T: PartialOrd + Clone, { - validate_guard( - raw_guard, - type_name, - validate_validators, - validate_sanitizers, - ) -} - -fn validate_validators( - validators: Vec>, -) -> Result>, syn::Error> -where - T: PartialOrd + Clone, -{ - validate_duplicates(&validators, |kind| { - format!( - "Duplicated validator `{kind}`.\nYou're a great engineer, but don't forget to take care of yourself!" - ) - })?; - - validate_numeric_bounds(&validators)?; - - let validators: Vec<_> = validators.into_iter().map(|v| v.item).collect(); - Ok(validators) -} - -fn validate_sanitizers( - sanitizers: Vec>, -) -> Result>, syn::Error> -where - T: PartialOrd + Clone, -{ - validate_duplicates(&sanitizers, |kind| { - format!("Duplicated sanitizer `{kind}`.\nIt happens, don't worry. We still love you!") - })?; - - let sanitizers: Vec<_> = sanitizers.into_iter().map(|s| s.item).collect(); - Ok(sanitizers) + validate_numeric_guard(raw_guard, type_name) } pub fn validate_integer_derive_traits( diff --git a/test_suite/tests/float.rs b/test_suite/tests/float.rs index d84e2313..8a77b542 100644 --- a/test_suite/tests/float.rs +++ b/test_suite/tests/float.rs @@ -489,7 +489,7 @@ mod traits { let err: DistParseError = "12.35".parse::().unwrap_err(); assert_eq!( err.to_string(), - "Failed to parse Dist: Dist is too big. The value must be less than 12.34." + "Failed to parse Dist: Dist is too big. The value must be less or equal to 12.34." ); }