bounded_integer/
parse.rs

1use core::fmt::{self, Display, Formatter};
2#[cfg(feature = "std")]
3use std::error::Error;
4
5macro_rules! from_str_radix_impl {
6    ($($ty:ident)*) => { $(
7        impl $crate::__private::Dispatch<$ty> {
8            // Implement it ourselves (copying the implementation from std) because `IntErrorKind`
9            // is non-exhaustive.
10            pub const fn from_ascii_radix(src: &[u8], radix: u32) -> Result<$ty, ParseError> {
11                assert!(
12                    2 <= radix && radix <= 36,
13                    "from_str_radix: radix must lie in the range `[2, 36]`",
14                );
15
16                macro_rules! yeet {
17                    ($e:expr) => { return Err(ParseError { kind: $e }) };
18                }
19
20                let (positive, digits) = match *src {
21                    [b'+', ref digits @ ..] => (true, digits),
22                    [b'-', ref digits @ ..] => (false, digits),
23                    ref digits => (true, digits),
24                };
25
26                if digits.is_empty() {
27                    yeet!(ParseErrorKind::NoDigits);
28                }
29
30                let overflow_kind = if positive {
31                    ParseErrorKind::AboveMax
32                } else {
33                    ParseErrorKind::BelowMin
34                };
35
36                let mut result: $ty = 0;
37
38                let mut i = 0;
39                while i < digits.len() {
40                    let digit = digits[i];
41
42                    let Some(digit_value) = (digit as char).to_digit(radix) else {
43                        yeet!(ParseErrorKind::InvalidDigit);
44                    };
45
46                    #[allow(clippy::cast_possible_wrap)]
47                    #[allow(clippy::cast_possible_truncation)]
48                    let Some(new_result) = result.checked_mul(radix as $ty) else {
49                        yeet!(overflow_kind);
50                    };
51
52                    #[allow(clippy::cast_possible_wrap)]
53                    #[allow(clippy::cast_possible_truncation)]
54                    let Some(new_result) = (if positive {
55                        new_result.checked_add(digit_value as $ty)
56                    } else {
57                        new_result.checked_sub(digit_value as $ty)
58                    }) else {
59                        yeet!(overflow_kind);
60                    };
61
62                    result = new_result;
63
64                    i += 1;
65                }
66
67                Ok(result)
68            }
69        }
70    )* }
71}
72from_str_radix_impl! { u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize }
73
74/// An error which can be returned when parsing a bounded integer.
75///
76/// This is the error type of all bounded integers' `from_str_radix()` functions (such as
77/// [`BoundedI8::from_str_radix`](crate::BoundedI8::from_str_radix)) as well as their
78/// [`FromStr`](std::str::FromStr) implementations.
79#[derive(Debug, Clone)]
80pub struct ParseError {
81    kind: ParseErrorKind,
82}
83
84impl ParseError {
85    /// Gives the cause of the error.
86    #[must_use]
87    pub fn kind(&self) -> ParseErrorKind {
88        self.kind
89    }
90}
91
92impl Display for ParseError {
93    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
94        match self.kind() {
95            ParseErrorKind::NoDigits => f.write_str("no digits found"),
96            ParseErrorKind::InvalidDigit => f.write_str("invalid digit found in string"),
97            ParseErrorKind::AboveMax => f.write_str("number too high to fit in target range"),
98            ParseErrorKind::BelowMin => f.write_str("number too low to fit in target range"),
99        }
100    }
101}
102
103#[cfg(feature = "std")]
104impl Error for ParseError {}
105
106/// The cause of the failure to parse the integer.
107#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108#[non_exhaustive]
109pub enum ParseErrorKind {
110    /// No digits were found in the input string.
111    ///
112    /// This happens when the input is an empty string, or when it only contains a `+` or `-`.
113    #[non_exhaustive]
114    NoDigits,
115    /// An invalid digit was found in the input.
116    #[non_exhaustive]
117    InvalidDigit,
118    /// The integer is too high to fit in the bounded integer's range.
119    #[non_exhaustive]
120    AboveMax,
121    /// The integer is too low to fit in the bounded integer's range.
122    #[non_exhaustive]
123    BelowMin,
124}
125
126#[must_use]
127pub const fn error_below_min() -> ParseError {
128    ParseError {
129        kind: ParseErrorKind::BelowMin,
130    }
131}
132#[must_use]
133pub const fn error_above_max() -> ParseError {
134    ParseError {
135        kind: ParseErrorKind::AboveMax,
136    }
137}