Skip to content

[v2] Replace error type with ones more like std #544

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ tokio = { default-features = false, features = ["macros", "rt-multi-thread", "te
version-sync = { default-features = false, features = ["html_root_url_updated", "markdown_deps_updated"], version = "0.9" }

[features]
alloc = []
c-repr = [] # Force Decimal to be repr(C)
db-diesel-mysql = ["db-diesel1-mysql"]
db-diesel-postgres = ["db-diesel1-postgres"]
Expand All @@ -76,7 +77,7 @@ serde-str = ["serde-with-str"]
serde-with-arbitrary-precision = ["serde", "serde_json/arbitrary_precision", "serde_json/std"]
serde-with-float = ["serde"]
serde-with-str = ["serde"]
std = []
std = ["alloc"]
tokio-pg = ["db-tokio-postgres"] # Backwards compatability

[workspace]
Expand Down
76 changes: 44 additions & 32 deletions src/decimal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::constants::{
UNSIGN_MASK,
};
use crate::ops;
use crate::Error;
use crate::{ParseDecimalError, TryFromDecimalError, TryIntoDecimalError};

#[cfg(feature = "rkyv-safe")]
use bytecheck::CheckBytes;
Expand Down Expand Up @@ -422,9 +422,9 @@ impl Decimal {
/// let max = Decimal::try_new(i64::MAX, u32::MAX);
/// assert!(max.is_err());
/// ```
pub const fn try_new(num: i64, scale: u32) -> crate::Result<Decimal> {
pub const fn try_new(num: i64, scale: u32) -> Result<Decimal, ParseDecimalError> {
if scale > MAX_PRECISION_U32 {
return Err(Error::ScaleExceedsMaximumPrecision(scale));
return Err(ParseDecimalError::Underflow);
}
let flags: u32 = scale << SCALE_SHIFT;
if num < 0 {
Expand Down Expand Up @@ -482,16 +482,16 @@ impl Decimal {
/// let max = Decimal::try_from_i128_with_scale(i128::MAX, u32::MAX);
/// assert!(max.is_err());
/// ```
pub const fn try_from_i128_with_scale(num: i128, scale: u32) -> crate::Result<Decimal> {
pub const fn try_from_i128_with_scale(num: i128, scale: u32) -> Result<Decimal, ParseDecimalError> {
if scale > MAX_PRECISION_U32 {
return Err(Error::ScaleExceedsMaximumPrecision(scale));
return Err(ParseDecimalError::Underflow);
}
let mut neg = false;
let mut wrapped = num;
if num > MAX_I128_REPR {
return Err(Error::ExceedsMaximumPossibleValue);
return Err(ParseDecimalError::PosOverflow);
} else if num < -MAX_I128_REPR {
return Err(Error::LessThanMinimumPossibleValue);
return Err(ParseDecimalError::NegOverflow);
} else if num < 0 {
neg = true;
wrapped = -num;
Expand Down Expand Up @@ -579,22 +579,30 @@ impl Decimal {
/// # Ok(())
/// # }
/// ```
pub fn from_scientific(value: &str) -> Result<Decimal, Error> {
pub fn from_scientific(value: &str) -> Result<Decimal, ParseDecimalError> {
const ERROR_MESSAGE: &str = "Failed to parse";
let std_int_err_to_decimal = |e: core::num::ParseIntError| match e.kind() {
core::num::IntErrorKind::Empty => ParseDecimalError::Empty,
core::num::IntErrorKind::InvalidDigit => ParseDecimalError::InvalidDigit,
core::num::IntErrorKind::PosOverflow => ParseDecimalError::PosOverflow,
core::num::IntErrorKind::NegOverflow => ParseDecimalError::NegOverflow,
core::num::IntErrorKind::Zero if cfg!(debug_assertions) => unreachable!(),
_ => ParseDecimalError::__Generic,
};

let mut split = value.splitn(2, |c| c == 'e' || c == 'E');

let base = split.next().ok_or_else(|| Error::from(ERROR_MESSAGE))?;
let exp = split.next().ok_or_else(|| Error::from(ERROR_MESSAGE))?;
let base = split.next().ok_or(ParseDecimalError::Empty)?;
let exp = split.next().ok_or(ParseDecimalError::__Generic)?;

let mut ret = Decimal::from_str(base)?;
let current_scale = ret.scale();

if let Some(stripped) = exp.strip_prefix('-') {
let exp: u32 = stripped.parse().map_err(|_| Error::from(ERROR_MESSAGE))?;
let exp: u32 = stripped.parse().map_err(std_int_err_to_decimal)?;
ret.set_scale(current_scale + exp)?;
} else {
let exp: u32 = exp.parse().map_err(|_| Error::from(ERROR_MESSAGE))?;
let exp: u32 = exp.parse().map_err(std_int_err_to_decimal)?;
if exp <= current_scale {
ret.set_scale(current_scale - exp)?;
} else if exp > 0 {
Expand All @@ -606,7 +614,7 @@ impl Decimal {
// Decimal type we effectively store the mantissa as 12,000,000,000 and scale as
// zero.
if exp > MAX_PRECISION_U32 {
return Err(Error::ScaleExceedsMaximumPrecision(exp));
return Err(ParseDecimalError::PosOverflow);
}
let mut exp = exp as usize;
// Max two iterations. If exp is 1 then it needs to index position 0 of the array.
Expand All @@ -628,7 +636,7 @@ impl Decimal {
};
match ret.checked_mul(pow) {
Some(r) => ret = r,
None => return Err(Error::ExceedsMaximumPossibleValue),
None => return Err(ParseDecimalError::PosOverflow),
};
}
ret.normalize_assign();
Expand Down Expand Up @@ -659,7 +667,7 @@ impl Decimal {
/// # Ok(())
/// # }
/// ```
pub fn from_str_radix(str: &str, radix: u32) -> Result<Self, crate::Error> {
pub fn from_str_radix(str: &str, radix: u32) -> Result<Self, ParseDecimalError> {
if radix == 10 {
crate::str::parse_str_radix_10(str)
} else {
Expand All @@ -676,16 +684,16 @@ impl Decimal {
///
/// ```
/// # use rust_decimal::prelude::*;
/// # use rust_decimal::Error;
/// # use rust_decimal::ParseDecimalError;
/// #
/// # fn main() -> Result<(), rust_decimal::Error> {
/// assert_eq!(Decimal::from_str_exact("0.001")?.to_string(), "0.001");
/// assert_eq!(Decimal::from_str_exact("0.00000_00000_00000_00000_00000_001")?.to_string(), "0.0000000000000000000000000001");
/// assert_eq!(Decimal::from_str_exact("0.00000_00000_00000_00000_00000_0001"), Err(Error::Underflow));
/// assert_eq!(Decimal::from_str_exact("0.00000_00000_00000_00000_00000_0001"), Err(ParseDecimalError::Underflow));
/// # Ok(())
/// # }
/// ```
pub fn from_str_exact(str: &str) -> Result<Self, crate::Error> {
pub fn from_str_exact(str: &str) -> Result<Self, ParseDecimalError> {
crate::str::parse_str_radix_10_exact(str)
}

Expand Down Expand Up @@ -824,9 +832,9 @@ impl Decimal {
/// # Ok(())
/// # }
/// ```
pub fn set_scale(&mut self, scale: u32) -> Result<(), Error> {
pub fn set_scale(&mut self, scale: u32) -> Result<(), ParseDecimalError> {
if scale > MAX_PRECISION_U32 {
return Err(Error::ScaleExceedsMaximumPrecision(scale));
return Err(ParseDecimalError::Underflow);
}
self.flags = (scale << SCALE_SHIFT) | (self.flags & SIGN_MASK);
Ok(())
Expand Down Expand Up @@ -1744,11 +1752,11 @@ macro_rules! impl_try_from_decimal {
"`.",
)]
impl TryFrom<Decimal> for $TInto {
type Error = crate::Error;
type Error = crate::TryFromDecimalError;

#[inline]
fn try_from(t: Decimal) -> Result<Self, Error> {
$conversion_fn(&t).ok_or_else(|| Error::ConversionTo(stringify!($TInto).into()))
fn try_from(t: Decimal) -> Result<Self, TryFromDecimalError> {
$conversion_fn(&t).ok_or_else(|| TryFromDecimalError { _priv: () })
}
}
};
Expand Down Expand Up @@ -1780,11 +1788,11 @@ macro_rules! impl_try_from_primitive {
"` into a `Decimal`.\n\nCan fail if the value is out of range for `Decimal`."
)]
impl TryFrom<$TFrom> for Decimal {
type Error = crate::Error;
type Error = TryIntoDecimalError;

#[inline]
fn try_from(t: $TFrom) -> Result<Self, Error> {
$conversion_fn(t).ok_or_else(|| Error::ConversionTo("Decimal".into()))
fn try_from(t: $TFrom) -> Result<Self, TryIntoDecimalError> {
$conversion_fn(t).ok_or_else(|| TryIntoDecimalError { _priv: () })
}
}
};
Expand Down Expand Up @@ -1818,8 +1826,8 @@ impl_from!(u16, FromPrimitive::from_u16);
impl_from!(u32, FromPrimitive::from_u32);
impl_from!(u64, FromPrimitive::from_u64);

impl_from!(i128, FromPrimitive::from_i128);
impl_from!(u128, FromPrimitive::from_u128);
impl_try_from_primitive!(i128, FromPrimitive::from_i128);
impl_try_from_primitive!(u128, FromPrimitive::from_u128);

impl Zero for Decimal {
fn zero() -> Decimal {
Expand Down Expand Up @@ -1872,17 +1880,17 @@ impl Signed for Decimal {
}

impl Num for Decimal {
type FromStrRadixErr = Error;
type FromStrRadixErr = ParseDecimalError;

fn from_str_radix(str: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> {
fn from_str_radix(str: &str, radix: u32) -> Result<Self, ParseDecimalError> {
Decimal::from_str_radix(str, radix)
}
}

impl FromStr for Decimal {
type Err = Error;
type Err = ParseDecimalError;

fn from_str(value: &str) -> Result<Decimal, Self::Err> {
fn from_str(value: &str) -> Result<Decimal, ParseDecimalError> {
crate::str::parse_str_radix_10(value)
}
}
Expand Down Expand Up @@ -2312,6 +2320,7 @@ impl ToPrimitive for Decimal {
}
}

#[cfg(feature = "alloc")]
impl fmt::Display for Decimal {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
let (rep, additional) = crate::str::to_str_internal(self, false, f.precision());
Expand All @@ -2324,18 +2333,21 @@ impl fmt::Display for Decimal {
}
}

#[cfg(feature = "alloc")]
impl fmt::Debug for Decimal {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
fmt::Display::fmt(self, f)
}
}

#[cfg(feature = "alloc")]
impl fmt::LowerExp for Decimal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
crate::str::fmt_scientific_notation(self, "e", f)
}
}

#[cfg(feature = "alloc")]
impl fmt::UpperExp for Decimal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
crate::str::fmt_scientific_notation(self, "E", f)
Expand Down
107 changes: 54 additions & 53 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,69 +1,70 @@
use crate::{constants::MAX_PRECISION_U32, Decimal};
use alloc::string::String;
#[cfg(doc)]
use crate::Decimal;
use core::fmt;

/// Error type for the library.
#[derive(Clone, Debug, PartialEq)]
pub enum Error {
/// A generic error from Rust Decimal with the `String` containing more information as to what
/// went wrong.
///
/// This is a legacy/deprecated error type retained for backwards compatibility.
ErrorString(String),
/// The value provided exceeds `Decimal::MAX`.
ExceedsMaximumPossibleValue,
/// The value provided is less than `Decimal::MIN`.
LessThanMinimumPossibleValue,
/// An underflow is when there are more fractional digits than can be represented within `Decimal`.
/// An error which can be returned when parsing [`Decimal`]s.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ParseDecimalError {
/// Value being parsed is empty.
Empty,
/// Contains an invalid digit in its context.
InvalidDigit,
/// Number is too large to fit in a `Decimal`.
PosOverflow,
/// Number is too small to fit in a `Decimal`.
NegOverflow,
/// Number has too many digits to fit in a `Decimal`.
Underflow,
/// The scale provided exceeds the maximum scale that `Decimal` can represent.
ScaleExceedsMaximumPrecision(u32),
/// Represents a failure to convert to/from `Decimal` to the specified type. This is typically
/// due to type constraints (e.g. `Decimal::MAX` cannot be converted into `i32`).
ConversionTo(String),
#[doc(hidden)]
__Internal,
#[doc(hidden)]
__Generic,
}

impl<S> From<S> for Error
where
S: Into<String>,
{
#[inline]
fn from(from: S) -> Self {
Self::ErrorString(from.into())
}
/// The error type returned when checked type conversion from [`Decimal`] fails.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TryFromDecimalError {
pub(crate) _priv: (),
}

#[cold]
pub(crate) fn tail_error(from: &'static str) -> Result<Decimal, Error> {
Err(from.into())
/// The error type returned when checked type conversion into [`Decimal`] fails.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TryIntoDecimalError {
pub(crate) _priv: (),
}

#[cfg(feature = "std")]
impl std::error::Error for Error {}
impl std::error::Error for ParseDecimalError {}

#[cfg(feature = "std")]
impl std::error::Error for TryFromDecimalError {}

impl fmt::Display for Error {
#[cfg(feature = "std")]
impl std::error::Error for TryIntoDecimalError {}

impl fmt::Display for ParseDecimalError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Self::ErrorString(ref err) => f.pad(err),
Self::ExceedsMaximumPossibleValue => {
write!(f, "Number exceeds maximum value that can be represented.")
}
Self::LessThanMinimumPossibleValue => {
write!(f, "Number less than minimum value that can be represented.")
}
Self::Underflow => {
write!(f, "Number has a high precision that can not be represented.")
}
Self::ScaleExceedsMaximumPrecision(ref scale) => {
write!(
f,
"Scale exceeds the maximum precision allowed: {} > {}",
scale, MAX_PRECISION_U32
)
}
Self::ConversionTo(ref type_name) => {
write!(f, "Error while converting to {}", type_name)
}
ParseDecimalError::Empty => "cannot parse decimal from empty string".fmt(f),
ParseDecimalError::InvalidDigit => "invalid digit found in string".fmt(f),
ParseDecimalError::PosOverflow => "number is too large to fit in a decimal".fmt(f),
ParseDecimalError::NegOverflow => "number is too small to fit in a decimal".fmt(f),
ParseDecimalError::Underflow => "number has too many digits to fit in a decimal".fmt(f),
ParseDecimalError::__Internal => "rust_decimal encountered an unexpected error condition".fmt(f),
ParseDecimalError::__Generic => "failed to parse".fmt(f),
}
}
}

impl fmt::Display for TryFromDecimalError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
"lossy conversion from decimal attempted".fmt(f)
}
}

impl fmt::Display for TryIntoDecimalError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
"lossy conversion into decimal attempted".fmt(f)
}
}
Loading