Implement `FromStr` for `Decimal`/`UDecimal`.

Signed-off-by: Jean Pierre Dudey <jeandudey@hotmail.com>
This commit is contained in:
Jean Pierre Dudey 2018-08-21 16:13:58 -04:00
parent 2d961412af
commit 07838568f9
1 changed files with 151 additions and 56 deletions

View File

@ -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,23 +128,10 @@ impl Decimal {
/// Returns whether or not the number is nonnegative
#[inline]
pub fn nonnegative(&self) -> bool { self.mantissa >= 0 }
}
#[cfg(feature = "serde-decimal")]
impl<'de> serde::Deserialize<'de> for Decimal {
/// Deserialize a `Decimal`.
///
/// This type is deserialized through [`strason`][1] for the same reason as
/// it's explained in the `Serialize` implementation.
///
/// [1]: https://github.com/apoelstra/strason
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let json = Json::deserialize(deserializer)?;
match json.num() {
Some(s) => {
// 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;
@ -162,13 +151,42 @@ impl<'de> serde::Deserialize<'de> for Decimal {
}
}
if negative { mantissa *= -1; }
Ok(Decimal {
Decimal {
mantissa: mantissa,
exponent: exponent,
})
}
None => Err(serde::de::Error::custom("expected decimal, got non-numeric"))
}
}
#[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<Self, Self::Err> {
Json::from_str(s)?
.num()
.map(Decimal::parse_decimal)
.ok_or(ParseDecimalError::NotANumber)
}
}
#[cfg(feature = "serde-decimal")]
impl<'de> serde::Deserialize<'de> for Decimal {
/// Deserialize a `Decimal`.
///
/// This type is deserialized through [`strason`][1] for the same reason as
/// it's explained in the `Serialize` implementation.
///
/// [1]: https://github.com/apoelstra/strason
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Json::deserialize(deserializer)?
.num()
.map(Decimal::parse_decimal)
.ok_or(serde::de::Error::custom("expected decimal, got non-numeric"))
}
}
@ -260,23 +278,10 @@ impl UDecimal {
self.mantissa * 10u64.pow((exponent - self.exponent) as u32)
}
}
}
#[cfg(feature = "serde-decimal")]
impl<'de> serde::Deserialize<'de> for UDecimal {
/// Deserialize an `UDecimal`.
///
/// This type is deserialized through [`strason`][1] for the same reason as
/// it's explained in the `Serialize` implementation.
///
/// [1]: https://github.com/apoelstra/strason
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let json = Json::deserialize(deserializer)?;
match json.num() {
Some(s) => {
// 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;
@ -293,13 +298,42 @@ impl<'de> serde::Deserialize<'de> for UDecimal {
_ => { /* whitespace or something, just ignore it */ }
}
}
Ok(UDecimal {
UDecimal {
mantissa: mantissa,
exponent: exponent,
})
}
None => Err(serde::de::Error::custom("expected decimal, got non-numeric"))
}
}
#[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<Self, Self::Err> {
Json::from_str(s)?
.num()
.map(UDecimal::parse_udecimal)
.ok_or(ParseDecimalError::NotANumber)
}
}
#[cfg(feature = "serde-decimal")]
impl<'de> serde::Deserialize<'de> for UDecimal {
/// Deserialize an `UDecimal`.
///
/// This type is deserialized through [`strason`][1] for the same reason as
/// it's explained in the `Serialize` implementation.
///
/// [1]: https://github.com/apoelstra/strason
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
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<strason::Error> 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::<Decimal>().unwrap();
assert_eq!(dec, Decimal::new(980000, 8));
let dec = "0.00980000".parse::<UDecimal>().unwrap();
assert_eq!(dec, UDecimal::new(980000, 8));
let dec = "0.00980".parse::<Decimal>().unwrap();
assert_eq!(dec, Decimal::new(98000, 7));
let dec = "0.00980".parse::<UDecimal>().unwrap();
assert_eq!(dec, UDecimal::new(98000, 7));
}
}