use crate::{Ratio, try_from_str};
use core::fmt::{self, Formatter};
use core::marker::PhantomData;
use core::ops::Mul;
use core::str::FromStr;
use num_integer::Integer;
use num_traits::Pow;
use serde::de::{self, Deserialize, Deserializer, Unexpected, Visitor};
#[derive(Clone, Copy, Debug)]
pub struct Rational<T>(pub Ratio<T>);
impl<'de, T> Deserialize<'de> for Rational<T>
where
T: Clone
+ From<u8>
+ FromStr
+ Integer
+ for<'a> Mul<&'a T, Output = T>
+ Pow<usize, Output = T>,
{
#[inline]
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct RationalVisitor<T> {
_x: PhantomData<fn() -> T>,
}
impl<T> Visitor<'_> for RationalVisitor<T>
where
T: Clone
+ From<u8>
+ FromStr
+ Integer
+ for<'a> Mul<&'a T, Output = T>
+ Pow<usize, Output = T>,
{
type Value = Rational<T>;
fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
formatter.write_str("struct Rational")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
try_from_str(v).map_or_else(
|_| {
Err(E::invalid_value(
Unexpected::Str(v),
&"a rational number in fraction or decimal notation",
))
},
|val| Ok(Rational(val)),
)
}
}
deserializer.deserialize_str(RationalVisitor { _x: PhantomData })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_serde() -> Result<(), serde_json::Error> {
assert_eq!(
Ratio::new(2u8, 3u8),
serde_json::from_str::<Rational::<u8>>(r#""2/3""#)?.0
);
assert_eq!(
Ratio::new(67u8, 100u8),
serde_json::from_str::<Rational::<u8>>(r#""0.67""#)?.0
);
Ok(())
}
}