From 07838568f92693d9cb7e3988129e8159d7521268 Mon Sep 17 00:00:00 2001 From: Jean Pierre Dudey Date: Tue, 21 Aug 2018 16:13:58 -0400 Subject: [PATCH] Implement `FromStr` for `Decimal`/`UDecimal`. Signed-off-by: Jean Pierre Dudey --- src/util/decimal.rs | 207 ++++++++++++++++++++++++++++++++------------ 1 file changed, 151 insertions(+), 56 deletions(-) diff --git a/src/util/decimal.rs b/src/util/decimal.rs index 9b839c6a..a6b74d33 100644 --- a/src/util/decimal.rs +++ b/src/util/decimal.rs @@ -22,9 +22,11 @@ //! use std::{fmt, ops}; +#[cfg(feature = "serde-decimal")] use std::error; +#[cfg(feature = "serde-decimal")] use std::str::FromStr; #[cfg(feature = "serde-decimal")] use serde; -#[cfg(feature = "serde-decimal")] use strason::Json; +#[cfg(feature = "serde-decimal")] use strason::{self, Json}; /// A fixed-point decimal type #[derive(Copy, Clone, Debug, Eq, Ord)] @@ -126,6 +128,47 @@ impl Decimal { /// Returns whether or not the number is nonnegative #[inline] pub fn nonnegative(&self) -> bool { self.mantissa >= 0 } + + // Converts a JSON number to a Decimal previously parsed by strason + #[cfg(feature = "serde-decimal")] + fn parse_decimal(s: &str) -> Decimal { + // We know this will be a well-formed Json number, so we can + // be pretty lax about parsing + let mut negative = false; + let mut past_dec = false; + let mut exponent = 0; + let mut mantissa = 0i64; + + for b in s.as_bytes() { + match *b { + b'-' => { negative = true; } + b'0'...b'9' => { + mantissa = 10 * mantissa + (b - b'0') as i64; + if past_dec { exponent += 1; } + } + b'.' => { past_dec = true; } + _ => { /* whitespace or something, just ignore it */ } + } + } + if negative { mantissa *= -1; } + Decimal { + mantissa: mantissa, + exponent: exponent, + } + } +} + +#[cfg(feature = "serde-decimal")] +impl FromStr for Decimal { + type Err = ParseDecimalError; + + /// Parses a `Decimal` from the given amount string. + fn from_str(s: &str) -> Result { + Json::from_str(s)? + .num() + .map(Decimal::parse_decimal) + .ok_or(ParseDecimalError::NotANumber) + } } #[cfg(feature = "serde-decimal")] @@ -140,35 +183,10 @@ impl<'de> serde::Deserialize<'de> for Decimal { where D: serde::Deserializer<'de>, { - let json = Json::deserialize(deserializer)?; - match json.num() { - Some(s) => { - // We know this will be a well-formed Json number, so we can - // be pretty lax about parsing - let mut negative = false; - let mut past_dec = false; - let mut exponent = 0; - let mut mantissa = 0i64; - - for b in s.as_bytes() { - match *b { - b'-' => { negative = true; } - b'0'...b'9' => { - mantissa = 10 * mantissa + (b - b'0') as i64; - if past_dec { exponent += 1; } - } - b'.' => { past_dec = true; } - _ => { /* whitespace or something, just ignore it */ } - } - } - if negative { mantissa *= -1; } - Ok(Decimal { - mantissa: mantissa, - exponent: exponent, - }) - } - None => Err(serde::de::Error::custom("expected decimal, got non-numeric")) - } + Json::deserialize(deserializer)? + .num() + .map(Decimal::parse_decimal) + .ok_or(serde::de::Error::custom("expected decimal, got non-numeric")) } } @@ -260,6 +278,44 @@ impl UDecimal { self.mantissa * 10u64.pow((exponent - self.exponent) as u32) } } + + // Converts a JSON number to a Decimal previously parsed by strason + #[cfg(feature = "serde-decimal")] + fn parse_udecimal(s: &str) -> UDecimal { + // We know this will be a well-formed Json number, so we can + // be pretty lax about parsing + let mut past_dec = false; + let mut exponent = 0; + let mut mantissa = 0u64; + + for b in s.as_bytes() { + match *b { + b'0'...b'9' => { + mantissa = 10 * mantissa + (b - b'0') as u64; + if past_dec { exponent += 1; } + } + b'.' => { past_dec = true; } + _ => { /* whitespace or something, just ignore it */ } + } + } + UDecimal { + mantissa: mantissa, + exponent: exponent, + } + } +} + +#[cfg(feature = "serde-decimal")] +impl FromStr for UDecimal { + type Err = ParseDecimalError; + + /// Parses a `UDecimal` from the given amount string. + fn from_str(s: &str) -> Result { + Json::from_str(s)? + .num() + .map(UDecimal::parse_udecimal) + .ok_or(ParseDecimalError::NotANumber) + } } #[cfg(feature = "serde-decimal")] @@ -274,32 +330,10 @@ impl<'de> serde::Deserialize<'de> for UDecimal { where D: serde::Deserializer<'de>, { - let json = Json::deserialize(deserializer)?; - match json.num() { - Some(s) => { - // We know this will be a well-formed Json number, so we can - // be pretty lax about parsing - let mut past_dec = false; - let mut exponent = 0; - let mut mantissa = 0u64; - - for b in s.as_bytes() { - match *b { - b'0'...b'9' => { - mantissa = 10 * mantissa + (b - b'0') as u64; - if past_dec { exponent += 1; } - } - b'.' => { past_dec = true; } - _ => { /* whitespace or something, just ignore it */ } - } - } - Ok(UDecimal { - mantissa: mantissa, - exponent: exponent, - }) - } - None => Err(serde::de::Error::custom("expected decimal, got non-numeric")) - } + Json::deserialize(deserializer)? + .num() + .map(UDecimal::parse_udecimal) + .ok_or(serde::de::Error::custom("expected decimal, got non-numeric")) } } @@ -321,6 +355,51 @@ impl serde::Serialize for UDecimal { } } +/// Errors that occur during `Decimal`/`UDecimal` parsing. +#[cfg(feature = "serde-decimal")] +#[derive(Debug)] +pub enum ParseDecimalError { + /// An error ocurred while parsing the JSON number. + Json(strason::Error), + /// Not a number. + NotANumber, +} + +#[cfg(feature = "serde-decimal")] +#[doc(hidden)] +impl From for ParseDecimalError { + fn from(e: strason::Error) -> ParseDecimalError { + ParseDecimalError::Json(e) + } +} + +#[cfg(feature = "serde-decimal")] +impl fmt::Display for ParseDecimalError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match *self { + ParseDecimalError::Json(ref e) => fmt::Display::fmt(e, fmt), + ParseDecimalError::NotANumber => fmt.write_str("not a valid JSON number") + } + } +} + +#[cfg(feature = "serde-decimal")] +impl error::Error for ParseDecimalError { + fn description(&self) -> &str { + match *self { + ParseDecimalError::Json(ref e) => e.description(), + ParseDecimalError::NotANumber => "not a valid JSON number", + } + } + + fn cause(&self) -> Option<&error::Error> { + match *self { + ParseDecimalError::Json(ref e) => Some(e), + _ => None, + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -468,4 +547,20 @@ mod tests { let dec: UDecimal = json.into_deserialize().unwrap(); assert_eq!(dec, UDecimal::new(98000, 7)); } + + #[test] + #[cfg(feature = "serde-decimal")] + fn parse_decimal_udecimal() { + let dec = "0.00980000".parse::().unwrap(); + assert_eq!(dec, Decimal::new(980000, 8)); + + let dec = "0.00980000".parse::().unwrap(); + assert_eq!(dec, UDecimal::new(980000, 8)); + + let dec = "0.00980".parse::().unwrap(); + assert_eq!(dec, Decimal::new(98000, 7)); + + let dec = "0.00980".parse::().unwrap(); + assert_eq!(dec, UDecimal::new(98000, 7)); + } }