rational.rs (3076B)
1 #![deny( 2 unsafe_code, 3 unused, 4 warnings, 5 clippy::all, 6 clippy::cargo, 7 clippy::nursery, 8 clippy::pedantic 9 )] 10 #![allow(clippy::implicit_return, clippy::missing_trait_methods)] 11 use crate::{try_from_str, Ratio}; 12 use core::fmt::{self, Formatter}; 13 use core::marker::PhantomData; 14 use core::ops::Mul; 15 use core::str::FromStr; 16 use num_integer::Integer; 17 use num_traits::Pow; 18 use serde::de::{self, Deserialize, Deserializer, Unexpected, Visitor}; 19 /// Wrapper around a `num_rational::Ratio` that 20 /// deserializes a JSON string representing a rational number in 21 /// fractional or decimal notation to a Ratio<T>. 22 #[allow(clippy::exhaustive_structs)] 23 pub struct Rational<T>(pub Ratio<T>); 24 #[allow(clippy::single_char_lifetime_names)] 25 impl<'de, T> Deserialize<'de> for Rational<T> 26 where 27 T: Clone 28 + From<u8> 29 + FromStr 30 + Integer 31 + for<'a> Mul<&'a T, Output = T> 32 + Pow<usize, Output = T>, 33 { 34 #[inline] 35 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 36 where 37 D: Deserializer<'de>, 38 { 39 /// Visitor used to deserialize a JSON string into a Rational. 40 struct RationalVisitor<T> { 41 /// Does not own nor drop a `T`. 42 _x: PhantomData<fn() -> T>, 43 } 44 #[allow(clippy::single_char_lifetime_names)] 45 impl<'de, T> Visitor<'de> for RationalVisitor<T> 46 where 47 T: Clone 48 + From<u8> 49 + FromStr 50 + Integer 51 + for<'a> Mul<&'a T, Output = T> 52 + Pow<usize, Output = T>, 53 { 54 type Value = Rational<T>; 55 fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { 56 formatter.write_str("struct Rational") 57 } 58 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 59 where 60 E: de::Error, 61 { 62 #[allow(clippy::unnecessary_wraps)] 63 /// Used as the closure that maps a `Ratio<R>`into an `Ok(Rational<R>)`. 64 #[inline] 65 const fn to_rational<R, E2>(val: Ratio<R>) -> Result<Rational<R>, E2> { 66 Ok(Rational(val)) 67 } 68 try_from_str(v).map_or_else( 69 |_| { 70 Err(E::invalid_value( 71 Unexpected::Str(v), 72 &"a rational number in fraction or decimal notation", 73 )) 74 }, 75 to_rational, 76 ) 77 } 78 } 79 deserializer.deserialize_str(RationalVisitor { _x: PhantomData }) 80 } 81 } 82 #[cfg(test)] 83 mod tests { 84 use super::*; 85 #[test] 86 fn test_serde() -> Result<(), serde_json::Error> { 87 assert_eq!( 88 Ratio::new(2u8, 3u8), 89 serde_json::from_str::<Rational::<u8>>(r#""2/3""#)?.0 90 ); 91 assert_eq!( 92 Ratio::new(67u8, 100u8), 93 serde_json::from_str::<Rational::<u8>>(r#""0.67""#)?.0 94 ); 95 Ok(()) 96 } 97 }