rh 0.1.14

A user-friendly command-line tool to request HTTP APis
Documentation
use std::cmp::Ordering;
use std::fmt::{self, Debug, Display};
use std::i64;

use serde::{Serialize, Serializer};

#[derive(Clone, PartialEq, PartialOrd)]
pub struct Number {
    n: N,
}

impl fmt::Display for Number {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        match self.n {
            N::PosInt(i) => Display::fmt(&i, formatter),
            N::NegInt(i) => Display::fmt(&i, formatter),
            N::Float(f) if f.is_nan() => formatter.write_str(".nan"),
            N::Float(f) if f.is_infinite() => {
                if f.is_sign_negative() {
                    formatter.write_str("-.inf")
                } else {
                    formatter.write_str(".inf")
                }
            }
            N::Float(f) => Display::fmt(&f, formatter),
        }
    }
}

impl Debug for Number {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        Debug::fmt(&self.n, formatter)
    }
}

#[derive(Copy, Clone, Debug)]
enum N {
    PosInt(u64),
    NegInt(i64),
    Float(f64),
}

impl Number {
    #[inline]
    pub fn from_str(n: &str) -> Option<Number> {
        match n.parse::<u64>() {
            Ok(num) => Some(Number { n: N::PosInt(num) }),
            _ => match n.parse::<i64>() {
                Ok(num) => Some(Number { n: N::NegInt(num) }),
                _ => match n.parse::<f64>() {
                    Ok(num) => Some(Number { n: N::Float(num) }),
                    _ => None,
                },
            },
        }
    }
}

impl PartialEq for N {
    fn eq(&self, other: &N) -> bool {
        match (*self, *other) {
            (N::PosInt(a), N::PosInt(b)) => a == b,
            (N::NegInt(a), N::NegInt(b)) => a == b,
            (N::Float(a), N::Float(b)) => {
                if a.is_nan() && b.is_nan() {
                    true
                } else {
                    a == b
                }
            }
            _ => false,
        }
    }
}

impl PartialOrd for N {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        match (*self, *other) {
            (N::Float(a), N::Float(b)) => {
                if a.is_nan() && b.is_nan() {
                    Some(Ordering::Equal)
                } else {
                    a.partial_cmp(&b)
                }
            }
            _ => Some(self.total_cmp(other)),
        }
    }
}

impl N {
    fn total_cmp(&self, other: &Self) -> Ordering {
        match (*self, *other) {
            (N::PosInt(a), N::PosInt(b)) => a.cmp(&b),
            (N::NegInt(a), N::NegInt(b)) => a.cmp(&b),
            (N::NegInt(_), N::PosInt(_)) => Ordering::Less,
            (N::PosInt(_), N::NegInt(_)) => Ordering::Greater,
            (N::Float(a), N::Float(b)) => a.partial_cmp(&b).unwrap_or_else(|| {
                if !a.is_nan() {
                    Ordering::Less
                } else if !b.is_nan() {
                    Ordering::Greater
                } else {
                    Ordering::Equal
                }
            }),
            (_, N::Float(_)) => Ordering::Less,
            (N::Float(_), _) => Ordering::Greater,
        }
    }
}

impl From<f64> for Number {
    #[inline]
    fn from(f: f64) -> Self {
        let n = { N::Float(f) };
        Number { n }
    }
}

impl Serialize for Number {
    #[inline]
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match self.n {
            N::PosInt(u) => serializer.serialize_u64(u),
            N::NegInt(i) => serializer.serialize_i64(i),
            N::Float(f) => serializer.serialize_f64(f),
        }
    }
}

macro_rules! impl_from_unsigned {
  (
      $($ty:ty),*
  ) => {
      $(
          impl From<$ty> for Number {
              #[inline]
              fn from(u: $ty) -> Self {
                  let n = {
                      N::PosInt(u as u64)
                  };
                  Number { n }
              }
          }
      )*
  };
}

macro_rules! impl_from_signed {
  (
      $($ty:ty),*
  ) => {
      $(
          impl From<$ty> for Number {
              #[inline]
              fn from(i: $ty) -> Self {
                  let n = {
                      if i < 0 {
                          N::NegInt(i as i64)
                      } else {
                          N::PosInt(i as u64)
                      }
                  };
                  Number { n }
              }
          }
      )*
  };
}

impl_from_unsigned!(u8, u16, u32, u64, usize);
impl_from_signed!(i8, i16, i32, i64, isize);

// UNIT TESTS /////////////////////////////////////////////////////////////////////////////

#[cfg(test)]
mod tests {
    use super::Number;

    #[test]
    fn detect_numbers_from_str() {
        assert_eq!(Number::from_str("123"), Some(123u64.into()));
        assert_eq!(Number::from_str("18446744073709551615"), Some(18446744073709551615u64.into()));
        assert_eq!(Number::from_str("18446744073709551615118446744073709551615"), Some(1.8446744073709552e40f64.into()));

        assert_eq!(Number::from_str("-123"), Some((-123).into()));
        assert_eq!(Number::from_str("-9223372036854775807"), Some((-9223372036854775807i64).into()));

        assert_eq!(Number::from_str("123.456"), Some((123.456f64).into()));
        assert_eq!(Number::from_str("18446744073709551615.456"), Some((18446744073709551615.456f64).into()));
        assert_eq!(Number::from_str("123e10"), Some((1230000000000.0f64).into()));

        assert_eq!(Number::from_str("a123"), None);
        assert_eq!(Number::from_str("123.a"), None);
        assert_eq!(Number::from_str("123e"), None);
        assert_eq!(Number::from_str("hello"), None);
    }
}