bounded_integer/
parse.rs

1use core::fmt::{self, Display, Formatter};
2#[cfg(feature = "std")]
3use std::error::Error;
4
5#[cfg_attr(not(any(feature = "types", feature = "macro")), expect(unused))]
6pub trait FromStrRadix: Sized {
7    fn from_str_radix(s: &str, radix: u32) -> Result<Self, ParseError>;
8}
9
10macro_rules! from_str_radix_impl {
11    ($($ty:ident)*) => { $(
12        impl FromStrRadix for $ty {
13            fn from_str_radix(src: &str, radix: u32) -> Result<Self, ParseError> {
14                assert!(
15                    (2..=36).contains(&radix),
16                    "from_str_radix: radix must lie in the range `[2, 36]` - found {}",
17                    radix,
18                );
19
20                let src = src.as_bytes();
21
22                let (positive, digits) = match *src {
23                    [b'+', ref digits @ ..] => (true, digits),
24                    [b'-', ref digits @ ..] => (false, digits),
25                    ref digits => (true, digits),
26                };
27
28                if digits.is_empty() {
29                    return Err(ParseError {
30                        kind: ParseErrorKind::NoDigits,
31                    });
32                }
33
34                let overflow_kind = if positive {
35                    ParseErrorKind::AboveMax
36                } else {
37                    ParseErrorKind::BelowMin
38                };
39
40                let mut result: Self = 0;
41
42                for &digit in digits {
43                    let digit_value =
44                        char::from(digit)
45                            .to_digit(radix)
46                            .ok_or_else(|| ParseError {
47                                kind: ParseErrorKind::InvalidDigit,
48                            })?;
49
50                    result = result
51                        .checked_mul(radix as Self)
52                        .ok_or_else(|| ParseError {
53                            kind: overflow_kind,
54                        })?;
55
56                    result = if positive {
57                        result.checked_add(digit_value as Self)
58                    } else {
59                        result.checked_sub(digit_value as Self)
60                    }
61                    .ok_or_else(|| ParseError {
62                        kind: overflow_kind,
63                    })?;
64                }
65
66                Ok(result)
67            }
68        }
69    )* }
70}
71from_str_radix_impl! { u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize }
72
73/// An error which can be returned when parsing a bounded integer.
74///
75/// This is the error type of all bounded integers' `from_str_radix()` functions (such as
76/// [`BoundedI8::from_str_radix`](crate::BoundedI8::from_str_radix)) as well as their
77/// [`FromStr`](std::str::FromStr) implementations.
78#[derive(Debug, Clone)]
79pub struct ParseError {
80    kind: ParseErrorKind,
81}
82
83impl ParseError {
84    /// Gives the cause of the error.
85    #[must_use]
86    pub fn kind(&self) -> ParseErrorKind {
87        self.kind
88    }
89}
90
91impl Display for ParseError {
92    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
93        match self.kind() {
94            ParseErrorKind::NoDigits => f.write_str("no digits found"),
95            ParseErrorKind::InvalidDigit => f.write_str("invalid digit found in string"),
96            ParseErrorKind::AboveMax => f.write_str("number too high to fit in target range"),
97            ParseErrorKind::BelowMin => f.write_str("number too low to fit in target range"),
98        }
99    }
100}
101
102#[cfg(feature = "std")]
103#[cfg_attr(doc_cfg, doc(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#[cfg_attr(not(any(feature = "types", feature = "macro")), expect(unused))]
127pub fn error_below_min() -> ParseError {
128    ParseError {
129        kind: ParseErrorKind::BelowMin,
130    }
131}
132#[cfg_attr(not(any(feature = "types", feature = "macro")), expect(unused))]
133pub fn error_above_max() -> ParseError {
134    ParseError {
135        kind: ParseErrorKind::AboveMax,
136    }
137}