Skip to main content

decimal_scaled/support/
error.rs

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