rational_extensions

Extends num_rational::Ratio<T>.
git clone https://git.philomathiclife.com/repos/rational_extensions
Log | Files | Refs | README

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&lt;T&gt;.
     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 }