From 564a8463ee07b2957ffdde228b3be791652af404 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Mon, 21 Jul 2025 18:11:41 +0200 Subject: [PATCH] More tests for `DefaultFilterable` + impl for floats --- askama/src/filters/default.rs | 171 +++++++++++++++++++++++++++++++++- 1 file changed, 169 insertions(+), 2 deletions(-) diff --git a/askama/src/filters/default.rs b/askama/src/filters/default.rs index 55dc71e1..fd6e0f4e 100644 --- a/askama/src/filters/default.rs +++ b/askama/src/filters/default.rs @@ -1,5 +1,5 @@ use core::convert::Infallible; -use core::num::{Saturating, Wrapping}; +use core::num::{self, FpCategory, Saturating, Wrapping}; use core::ops::Deref; use core::pin::Pin; @@ -58,6 +58,7 @@ impl_for_ref! { } } +/// A [pinned][Pin] reference has a value if the referenced data has a value. impl DefaultFilterable for Pin where T: Deref, @@ -76,6 +77,7 @@ where } } +/// An [`Option`] has a value if it is `Some`. impl DefaultFilterable for Option { type Filtered<'a> = &'a T @@ -90,6 +92,7 @@ impl DefaultFilterable for Option { } } +/// A [`Result`] has a value if it is `Ok`. impl DefaultFilterable for Result { type Filtered<'a> = &'a T @@ -104,6 +107,7 @@ impl DefaultFilterable for Result { } } +/// A [`str`] has a value if it is not empty. impl DefaultFilterable for str { type Filtered<'a> = &'a str @@ -121,6 +125,7 @@ impl DefaultFilterable for str { } } +/// A [`String`][alloc::string::String] has a value if it is not empty. #[cfg(feature = "alloc")] impl DefaultFilterable for alloc::string::String { type Filtered<'a> @@ -136,6 +141,7 @@ impl DefaultFilterable for alloc::string::String { } } +/// A [`Cow`][alloc::borrow::Cow] has a value if it's borrowed data has a value. #[cfg(feature = "alloc")] impl DefaultFilterable for alloc::borrow::Cow<'_, T> @@ -153,6 +159,7 @@ impl DefaultFilterable } } +/// A [`Wrapping`] integer has a value if it is not `0`. impl DefaultFilterable for Wrapping { type Filtered<'a> = T::Filtered<'a> @@ -167,6 +174,7 @@ impl DefaultFilterable for Wrapping { } } +/// A [`Saturating`] integer has a value if it is not `0`. impl DefaultFilterable for Saturating { type Filtered<'a> = T::Filtered<'a> @@ -183,6 +191,7 @@ impl DefaultFilterable for Saturating { macro_rules! impl_for_int { ($($ty:ty)*) => { $( + #[doc = concat!("A [`", stringify!($ty), "`] has a value if it is not `0`.")] impl DefaultFilterable for $ty { type Filtered<'a> = $ty; type Error = Infallible; @@ -205,7 +214,8 @@ impl_for_int!( macro_rules! impl_for_non_zero { ($($name:ident : $ty:ty)*) => { $( - impl DefaultFilterable for core::num::$name { + #[doc = concat!("A [`", stringify!($name), "`][num::", stringify!($name),"] always has a value.")] + impl DefaultFilterable for num::$name { type Filtered<'a> = $ty; type Error = Infallible; @@ -222,6 +232,7 @@ impl_for_non_zero!( NonZeroI8:i8 NonZeroI16:i16 NonZeroI32:i32 NonZeroI64:i64 NonZeroI128:i128 NonZeroIsize:isize ); +/// A `bool` has a value if it is [`true`]. impl DefaultFilterable for bool { type Filtered<'a> = bool; type Error = Infallible; @@ -234,3 +245,159 @@ impl DefaultFilterable for bool { } } } + +/// An `f32` has a value if it is [`Normal`][FpCategory::Normal], i.e. it is not zero, +/// not sub-normal, not infinite and not NaN. +impl DefaultFilterable for f32 { + type Filtered<'a> + = Self + where + Self: 'a; + + type Error = Infallible; + + #[inline] + fn as_filtered(&self) -> Result>, Self::Error> { + Ok((self.classify() == FpCategory::Normal).then_some(*self)) + } +} + +/// An `f64` has a value if it is [`Normal`](FpCategory::Normal), i.e. it is not zero, +/// not sub-normal, not infinite and not NaN. +impl DefaultFilterable for f64 { + type Filtered<'a> + = Self + where + Self: 'a; + + type Error = Infallible; + + #[inline] + fn as_filtered(&self) -> Result>, Self::Error> { + Ok((self.classify() == FpCategory::Normal).then_some(*self)) + } +} + +#[test] +#[cfg(feature = "std")] +fn test_default_filterable() { + use std::borrow::Cow; + use std::rc::Rc; + use std::string::ToString; + use std::sync::{Arc, Mutex}; + + use assert_matches::assert_matches; + + // integers + assert_matches!(0_u8.as_filtered(), Ok(None)); + assert_matches!(0_u16.as_filtered(), Ok(None)); + assert_matches!(0_u32.as_filtered(), Ok(None)); + assert_matches!(0_u64.as_filtered(), Ok(None)); + assert_matches!(0_u128.as_filtered(), Ok(None)); + assert_matches!(0_usize.as_filtered(), Ok(None)); + assert_matches!(0_i8.as_filtered(), Ok(None)); + assert_matches!(0_i16.as_filtered(), Ok(None)); + assert_matches!(0_i32.as_filtered(), Ok(None)); + assert_matches!(0_i64.as_filtered(), Ok(None)); + assert_matches!(0_i128.as_filtered(), Ok(None)); + assert_matches!(0_isize.as_filtered(), Ok(None)); + assert_matches!(1_u8.as_filtered(), Ok(Some(1))); + assert_matches!(1_u16.as_filtered(), Ok(Some(1))); + assert_matches!(1_u32.as_filtered(), Ok(Some(1))); + assert_matches!(1_u64.as_filtered(), Ok(Some(1))); + assert_matches!(1_u128.as_filtered(), Ok(Some(1))); + assert_matches!(1_usize.as_filtered(), Ok(Some(1))); + assert_matches!(1_i8.as_filtered(), Ok(Some(1))); + assert_matches!(1_i16.as_filtered(), Ok(Some(1))); + assert_matches!(1_i32.as_filtered(), Ok(Some(1))); + assert_matches!(1_i64.as_filtered(), Ok(Some(1))); + assert_matches!(1_i128.as_filtered(), Ok(Some(1))); + assert_matches!(1_isize.as_filtered(), Ok(Some(1))); + assert_matches!((-1_i8).as_filtered(), Ok(Some(-1))); + assert_matches!((-1_i16).as_filtered(), Ok(Some(-1))); + assert_matches!((-1_i32).as_filtered(), Ok(Some(-1))); + assert_matches!((-1_i64).as_filtered(), Ok(Some(-1))); + assert_matches!((-1_i128).as_filtered(), Ok(Some(-1))); + assert_matches!((-1_isize).as_filtered(), Ok(Some(-1))); + + // floats + // -> zero + assert_matches!(0_f32.as_filtered(), Ok(None)); + assert_matches!(0_f64.as_filtered(), Ok(None)); + // -> subnormal + assert_matches!((f32::MIN_POSITIVE / 2.0).as_filtered(), Ok(None)); + assert_matches!((f64::MIN_POSITIVE / 2.0).as_filtered(), Ok(None)); + // -> nan + assert_matches!(f32::NAN.as_filtered(), Ok(None)); + assert_matches!(f64::NAN.as_filtered(), Ok(None)); + // -> infinite + assert_matches!(f32::NEG_INFINITY.as_filtered(), Ok(None)); + assert_matches!(f32::INFINITY.as_filtered(), Ok(None)); + assert_matches!(f64::NEG_INFINITY.as_filtered(), Ok(None)); + assert_matches!(f64::INFINITY.as_filtered(), Ok(None)); + // -> normal + assert_matches!(1_f32.as_filtered(), Ok(Some(1.0))); + assert_matches!((-1_f32).as_filtered(), Ok(Some(-1.0))); + assert_matches!(f32::MIN.as_filtered(), Ok(Some(f32::MIN))); + assert_matches!(f32::MIN_POSITIVE.as_filtered(), Ok(Some(f32::MIN_POSITIVE))); + assert_matches!(f32::MAX.as_filtered(), Ok(Some(f32::MAX))); + assert_matches!(1_f64.as_filtered(), Ok(Some(1.0))); + assert_matches!((-1_f64).as_filtered(), Ok(Some(-1.0))); + assert_matches!(f64::MIN.as_filtered(), Ok(Some(f64::MIN))); + assert_matches!(f64::MIN_POSITIVE.as_filtered(), Ok(Some(f64::MIN_POSITIVE))); + assert_matches!(f64::MAX.as_filtered(), Ok(Some(f64::MAX))); + + // non-zero integers + assert_matches!(num::NonZeroU8::new(1).unwrap().as_filtered(), Ok(Some(1))); + assert_matches!(num::NonZeroU16::new(1).unwrap().as_filtered(), Ok(Some(1))); + assert_matches!(num::NonZeroU32::new(1).unwrap().as_filtered(), Ok(Some(1))); + assert_matches!(num::NonZeroU64::new(1).unwrap().as_filtered(), Ok(Some(1))); + assert_matches!(num::NonZeroU128::new(1).unwrap().as_filtered(), Ok(Some(1))); + assert_matches!( + num::NonZeroUsize::new(1).unwrap().as_filtered(), + Ok(Some(1)) + ); + assert_matches!(num::NonZeroI8::new(1).unwrap().as_filtered(), Ok(Some(1))); + assert_matches!(num::NonZeroI16::new(1).unwrap().as_filtered(), Ok(Some(1))); + assert_matches!(num::NonZeroI32::new(1).unwrap().as_filtered(), Ok(Some(1))); + assert_matches!(num::NonZeroI64::new(1).unwrap().as_filtered(), Ok(Some(1))); + assert_matches!(num::NonZeroI128::new(1).unwrap().as_filtered(), Ok(Some(1))); + assert_matches!( + num::NonZeroIsize::new(1).unwrap().as_filtered(), + Ok(Some(1)) + ); + + // strings + assert_matches!("".as_filtered(), Ok(None)); + assert_matches!("hello".as_filtered(), Ok(Some("hello"))); + assert_matches!("".to_string().as_filtered(), Ok(None)); + assert_matches!("hello".to_string().as_filtered(), Ok(Some("hello"))); + assert_matches!(Cow::Borrowed("").as_filtered(), Ok(None)); + assert_matches!(Cow::Borrowed("hello").as_filtered(), Ok(Some("hello"))); + assert_matches!(Cow::::Owned("".to_string()).as_filtered(), Ok(None)); + assert_matches!( + Cow::::Owned("hello".to_string()).as_filtered(), + Ok(Some("hello")) + ); + + // results + options + assert_matches!(Ok::<(), ()>(()).as_filtered(), Ok(Some(()))); + assert_matches!(Err::<(), ()>(()).as_filtered(), Ok(None)); + assert_matches!(Some(()).as_filtered(), Ok(Some(()))); + assert_matches!(None::<()>.as_filtered(), Ok(None)); + + // references + assert_matches!(Arc::new("").as_filtered(), Ok(None)); + assert_matches!(Arc::new("hello").as_filtered(), Ok(Some("hello"))); + assert_matches!(Arc::pin("").as_filtered(), Ok(None)); + assert_matches!(Arc::pin("hello").as_filtered(), Ok(Some("hello"))); + assert_matches!(Rc::new("").as_filtered(), Ok(None)); + assert_matches!(Rc::new("hello").as_filtered(), Ok(Some("hello"))); + assert_matches!(Rc::pin("").as_filtered(), Ok(None)); + assert_matches!(Rc::pin("hello").as_filtered(), Ok(Some("hello"))); + assert_matches!(Mutex::new("").try_lock().unwrap().as_filtered(), Ok(None)); + assert_matches!( + Mutex::new("hello").try_lock().unwrap().as_filtered(), + Ok(Some("hello")) + ); +}