Skip to main content

decimal_scaled/
error.rs

1//! Error types used across the decimal-scaled crate.
2//!
3//! Two enums live here:
4//!
5//! - [`ConvertError`] — returned by fallible `TryFrom` impls between
6//! primitive types (`i128`, `u128`, `f32`, `f64`) and any decimal
7//! width. Distinguishes overflow from non-finite float input.
8//! - [`ParseError`] — returned by `FromStr` when the input is not a
9//! valid canonical decimal literal. One variant per failure mode so
10//! callers can surface a precise diagnostic.
11//!
12//! Both types are width-neutral — the same enum is returned by every
13//! width (D9, D18, D38, D76, D153, D307).
14
15/// Error returned by the fallible [`TryFrom`] impls.
16///
17/// Covers the two distinct failure modes:
18/// - [`ConvertError::Overflow`] — the input, after scaling by
19/// `10^SCALE`, exceeds the destination's representable range.
20/// - [`ConvertError::NotFinite`] — the float input is `NaN`,
21/// `+inf`, or `-inf`.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23pub enum ConvertError {
24    /// Input magnitude is outside the destination type's range after scaling.
25    Overflow,
26    /// Input is `NaN`, `+inf`, or `-inf` (only reachable from the
27    /// `TryFrom<f32>` / `TryFrom<f64>` impls).
28    NotFinite,
29}
30
31impl core::fmt::Display for ConvertError {
32    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
33        match self {
34            Self::Overflow => f.write_str("decimal conversion overflow"),
35            Self::NotFinite => f.write_str("decimal conversion from non-finite float"),
36        }
37    }
38}
39
40#[cfg(feature = "std")]
41impl std::error::Error for ConvertError {}
42
43/// Error returned by `FromStr` when the input is not a valid canonical
44/// decimal literal.
45///
46/// Each variant identifies one specific failure mode so callers can
47/// surface a precise diagnostic.
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
49pub enum ParseError {
50    /// Input string is empty.
51    Empty,
52    /// Input is `-` or `+` with no following digits.
53    SignOnly,
54    /// Integer part has a redundant leading zero (e.g. `01.5`).
55    LeadingZero,
56    /// Fractional part has more digits than `SCALE`.
57    OverlongFractional,
58    /// Input uses scientific notation (`1e3`); not accepted.
59    ScientificNotation,
60    /// Input contains a character that is not a digit, sign, or dot.
61    InvalidChar,
62    /// Parsed value exceeds the destination type's range after scaling.
63    OutOfRange,
64    /// Decimal point with no digit on one side (e.g. `.5` or `5.`).
65    MissingDigits,
66}
67
68impl core::fmt::Display for ParseError {
69    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
70        let msg = match self {
71            Self::Empty => "empty input",
72            Self::SignOnly => "sign with no digits",
73            Self::LeadingZero => "redundant leading zero in integer part",
74            Self::OverlongFractional => "fractional part exceeds SCALE digits",
75            Self::ScientificNotation => "scientific notation not accepted",
76            Self::InvalidChar => "invalid character",
77            Self::OutOfRange => "value out of representable range",
78            Self::MissingDigits => "decimal point with no adjacent digits",
79        };
80        f.write_str(msg)
81    }
82}
83
84#[cfg(feature = "std")]
85impl std::error::Error for ParseError {}