bitcoin_units/
parse.rs

1// SPDX-License-Identifier: CC0-1.0
2
3//! Parsing utilities.
4
5use alloc::string::String;
6use core::fmt;
7use core::str::FromStr;
8
9use internals::write_err;
10
11/// Error with rich context returned when a string can't be parsed as an integer.
12///
13/// This is an extension of [`core::num::ParseIntError`], which carries the input that failed to
14/// parse as well as type information. As a result it provides very informative error messages that
15/// make it easier to understand the problem and correct mistakes.
16///
17/// Note that this is larger than the type from `core` so if it's passed through a deep call stack
18/// in a performance-critical application you may want to box it or throw away the context by
19/// converting to `core` type.
20#[derive(Debug, Clone, PartialEq, Eq)]
21#[non_exhaustive]
22pub struct ParseIntError {
23    pub(crate) input: String,
24    // for displaying - see Display impl with nice error message below
25    bits: u8,
26    // We could represent this as a single bit but it wouldn't actually derease the cost of moving
27    // the struct because String contains pointers so there will be padding of bits at least
28    // pointer_size - 1 bytes: min 1B in practice.
29    is_signed: bool,
30    pub(crate) source: core::num::ParseIntError,
31}
32
33impl ParseIntError {
34    /// Returns the input that was attempted to be parsed.
35    pub fn input(&self) -> &str { &self.input }
36}
37
38impl fmt::Display for ParseIntError {
39    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
40        let signed = if self.is_signed { "signed" } else { "unsigned" };
41        let n = if self.bits == 8 { "n" } else { "" };
42        write_err!(f, "failed to parse '{}' as a{} {}-bit {} integer", self.input, n, self.bits, signed; self.source)
43    }
44}
45
46#[cfg(feature = "std")]
47impl std::error::Error for ParseIntError {
48    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.source) }
49}
50
51impl From<ParseIntError> for core::num::ParseIntError {
52    fn from(value: ParseIntError) -> Self { value.source }
53}
54
55impl AsRef<core::num::ParseIntError> for ParseIntError {
56    fn as_ref(&self) -> &core::num::ParseIntError { &self.source }
57}
58
59/// Not strictly necessary but serves as a lint - avoids weird behavior if someone accidentally
60/// passes non-integer to the `parse()` function.
61pub trait Integer: FromStr<Err = core::num::ParseIntError> + TryFrom<i8> + Sized {}
62
63macro_rules! impl_integer {
64    ($($type:ty),* $(,)?) => {
65        $(
66        impl Integer for $type {}
67        )*
68    }
69}
70
71impl_integer!(u8, i8, u16, i16, u32, i32, u64, i64, u128, i128);
72
73/// Parses the input string as an integer returning an error carrying rich context.
74///
75/// If the caller owns `String` or `Box<str>` which is not used later it's better to pass it as
76/// owned since it avoids allocation in error case.
77pub fn int<T: Integer, S: AsRef<str> + Into<String>>(s: S) -> Result<T, ParseIntError> {
78    s.as_ref().parse().map_err(|error| {
79        ParseIntError {
80            input: s.into(),
81            bits: u8::try_from(core::mem::size_of::<T>() * 8).expect("max is 128 bits for u128"),
82            // We detect if the type is signed by checking if -1 can be represented by it
83            // this way we don't have to implement special traits and optimizer will get rid of the
84            // computation.
85            is_signed: T::try_from(-1i8).is_ok(),
86            source: error,
87        }
88    })
89}
90
91/// Implements `TryFrom<$from> for $to` using `parse::int`, mapping the output using infallible
92/// conversion function `fn`.
93#[macro_export]
94macro_rules! impl_tryfrom_str_from_int_infallible {
95    ($($from:ty, $to:ident, $inner:ident, $fn:ident);*) => {
96        $(
97        impl core::convert::TryFrom<$from> for $to {
98            type Error = $crate::parse::ParseIntError;
99
100            fn try_from(s: $from) -> core::result::Result<Self, Self::Error> {
101                $crate::parse::int::<$inner, $from>(s).map($to::$fn)
102            }
103        }
104        )*
105    }
106}
107
108/// Implements `FromStr` and `TryFrom<{&str, String, Box<str>}> for $to` using `parse::int`, mapping
109/// the output using infallible conversion function `fn`.
110///
111/// The `Error` type is `ParseIntError`
112#[macro_export]
113macro_rules! impl_parse_str_from_int_infallible {
114    ($to:ident, $inner:ident, $fn:ident) => {
115        #[cfg(all(feature = "alloc", not(feature = "std")))]
116        $crate::impl_tryfrom_str_from_int_infallible!(&str, $to, $inner, $fn; alloc::string::String, $to, $inner, $fn; alloc::boxed::Box<str>, $to, $inner, $fn);
117        #[cfg(feature = "std")]
118        $crate::impl_tryfrom_str_from_int_infallible!(&str, $to, $inner, $fn; std::string::String, $to, $inner, $fn; std::boxed::Box<str>, $to, $inner, $fn);
119
120        impl core::str::FromStr for $to {
121            type Err = $crate::parse::ParseIntError;
122
123            fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
124                $crate::parse::int::<$inner, &str>(s).map($to::$fn)
125            }
126        }
127
128    }
129}
130
131/// Implements `TryFrom<$from> for $to`.
132#[macro_export]
133macro_rules! impl_tryfrom_str {
134    ($($from:ty, $to:ty, $err:ty, $inner_fn:expr);*) => {
135        $(
136            impl core::convert::TryFrom<$from> for $to {
137                type Error = $err;
138
139                fn try_from(s: $from) -> core::result::Result<Self, Self::Error> {
140                    $inner_fn(s)
141                }
142            }
143        )*
144    }
145}
146
147/// Implements standard parsing traits for `$type` by calling into `$inner_fn`.
148#[macro_export]
149macro_rules! impl_parse_str {
150    ($to:ty, $err:ty, $inner_fn:expr) => {
151        $crate::impl_tryfrom_str!(&str, $to, $err, $inner_fn; String, $to, $err, $inner_fn; Box<str>, $to, $err, $inner_fn);
152
153        impl core::str::FromStr for $to {
154            type Err = $err;
155
156            fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
157                $inner_fn(s)
158            }
159        }
160    }
161}
162
163/// Removes the prefix `0x` (or `0X`) from a hex string.
164///
165/// # Errors
166///
167/// If the input string does not contain a prefix.
168pub fn hex_remove_prefix(s: &str) -> Result<&str, PrefixedHexError> {
169    if let Some(checked) = s.strip_prefix("0x") {
170        Ok(checked)
171    } else if let Some(checked) = s.strip_prefix("0X") {
172        Ok(checked)
173    } else {
174        Err(MissingPrefixError::new(s.into()).into())
175    }
176}
177
178/// Checks a hex string does not have a prefix `0x` (or `0X`).
179///
180/// # Errors
181///
182/// If the input string contains a prefix.
183pub fn hex_check_unprefixed(s: &str) -> Result<&str, UnprefixedHexError> {
184    if s.starts_with("0x") || s.starts_with("0X") {
185        return Err(ContainsPrefixError::new(s.into()).into());
186    }
187    Ok(s)
188}
189
190/// Parses a `u32` from a hex string.
191///
192/// Input string may or may not contain a `0x` (or `0X`) prefix.
193///
194/// # Errors
195///
196/// If the input string is not a valid hex encoding of a `u32`.
197pub fn hex_u32(s: &str) -> Result<u32, ParseIntError> {
198    let unchecked = hex_remove_optional_prefix(s);
199    Ok(hex_u32_unchecked(unchecked)?)
200}
201
202/// Parses a `u32` from a prefixed hex string.
203///
204/// # Errors
205///
206/// - If the input string does not contain a `0x` (or `0X`) prefix.
207/// - If the input string is not a valid hex encoding of a `u32`.
208pub fn hex_u32_prefixed(s: &str) -> Result<u32, PrefixedHexError> {
209    let checked = hex_remove_prefix(s)?;
210    Ok(hex_u32_unchecked(checked)?)
211}
212
213/// Parses a `u32` from an unprefixed hex string.
214///
215/// # Errors
216///
217/// - If the input string contains a `0x` (or `0X`) prefix.
218/// - If the input string is not a valid hex encoding of a `u32`.
219pub fn hex_u32_unprefixed(s: &str) -> Result<u32, UnprefixedHexError> {
220    let checked = hex_check_unprefixed(s)?;
221    Ok(hex_u32_unchecked(checked)?)
222}
223
224/// Parses a `u32` from an unprefixed hex string without first checking for a prefix.
225///
226/// # Errors
227///
228/// - If the input string contains a `0x` (or `0X`) prefix, returns `InvalidDigit` due to the `x`.
229/// - If the input string is not a valid hex encoding of a `u32`.
230pub fn hex_u32_unchecked(s: &str) -> Result<u32, ParseIntError> {
231    u32::from_str_radix(s, 16).map_err(|error| ParseIntError {
232        input: s.into(),
233        bits: 32,
234        is_signed: false,
235        source: error,
236    })
237}
238
239/// Parses a `u128` from a hex string.
240///
241/// Input string may or may not contain a `0x` (or `0X`) prefix.
242///
243/// # Errors
244///
245/// If the input string is not a valid hex encoding of a `u128`.
246pub fn hex_u128(s: &str) -> Result<u128, ParseIntError> {
247    let unchecked = hex_remove_optional_prefix(s);
248    Ok(hex_u128_unchecked(unchecked)?)
249}
250
251/// Parses a `u128` from a hex string.
252///
253/// # Errors
254///
255/// - If the input string does not contain a `0x` (or `0X`) prefix.
256/// - If the input string is not a valid hex encoding of a `u128`.
257pub fn hex_u128_prefixed(s: &str) -> Result<u128, PrefixedHexError> {
258    let checked = hex_remove_prefix(s)?;
259    Ok(hex_u128_unchecked(checked)?)
260}
261
262/// Parses a `u128` from a hex string.
263///
264/// # Errors
265///
266/// - If the input string contains a `0x` (or `0X`) prefix.
267/// - If the input string is not a valid hex encoding of a `u128`.
268pub fn hex_u128_unprefixed(s: &str) -> Result<u128, UnprefixedHexError> {
269    let checked = hex_check_unprefixed(s)?;
270    Ok(hex_u128_unchecked(checked)?)
271}
272
273/// Parses a `u128` from an unprefixed hex string without first checking for a prefix.
274///
275/// # Errors
276///
277/// - If the input string contains a `0x` (or `0X`) prefix, returns `InvalidDigit` due to the `x`.
278/// - If the input string is not a valid hex encoding of a `u128`.
279pub fn hex_u128_unchecked(s: &str) -> Result<u128, ParseIntError> {
280    u128::from_str_radix(s, 16).map_err(|error| ParseIntError {
281        input: s.into(),
282        bits: 128,
283        is_signed: false,
284        source: error,
285    })
286}
287
288/// Strips the hex prefix off `s` if one is present.
289pub(crate) fn hex_remove_optional_prefix(s: &str) -> &str {
290    if let Some(stripped) = s.strip_prefix("0x") {
291        stripped
292    } else if let Some(stripped) = s.strip_prefix("0X") {
293        stripped
294    } else {
295        s
296    }
297}
298
299/// Error returned when parsing an integer from a hex string that is supposed to contain a prefix.
300#[derive(Debug, Clone, Eq, PartialEq)]
301pub enum PrefixedHexError {
302    /// Hex string is missing prefix.
303    MissingPrefix(MissingPrefixError),
304    /// Error parsing integer from hex string.
305    ParseInt(ParseIntError),
306}
307
308impl fmt::Display for PrefixedHexError {
309    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
310        use PrefixedHexError::*;
311
312        match *self {
313            MissingPrefix(ref e) => write_err!(f, "hex string is missing prefix"; e),
314            ParseInt(ref e) => write_err!(f, "prefixed hex string invalid int"; e),
315        }
316    }
317}
318
319#[cfg(feature = "std")]
320impl std::error::Error for PrefixedHexError {
321    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
322        use PrefixedHexError::*;
323
324        match *self {
325            MissingPrefix(ref e) => Some(e),
326            ParseInt(ref e) => Some(e),
327        }
328    }
329}
330
331impl From<MissingPrefixError> for PrefixedHexError {
332    fn from(e: MissingPrefixError) -> Self { Self::MissingPrefix(e) }
333}
334
335impl From<ParseIntError> for PrefixedHexError {
336    fn from(e: ParseIntError) -> Self { Self::ParseInt(e) }
337}
338
339/// Error returned when parsing an integer from a hex string that is not supposed to contain a prefix.
340#[derive(Debug, Clone, Eq, PartialEq)]
341pub enum UnprefixedHexError {
342    /// Hex string contains prefix.
343    ContainsPrefix(ContainsPrefixError),
344    /// Error parsing integer from string.
345    ParseInt(ParseIntError),
346}
347
348impl fmt::Display for UnprefixedHexError {
349    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
350        use UnprefixedHexError::*;
351
352        match *self {
353            ContainsPrefix(ref e) => write_err!(f, "hex string is contains prefix"; e),
354            ParseInt(ref e) => write_err!(f, "hex string parse int"; e),
355        }
356    }
357}
358
359#[cfg(feature = "std")]
360impl std::error::Error for UnprefixedHexError {
361    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
362        use UnprefixedHexError::*;
363
364        match *self {
365            ContainsPrefix(ref e) => Some(e),
366            ParseInt(ref e) => Some(e),
367        }
368    }
369}
370
371impl From<ContainsPrefixError> for UnprefixedHexError {
372    fn from(e: ContainsPrefixError) -> Self { Self::ContainsPrefix(e) }
373}
374
375impl From<ParseIntError> for UnprefixedHexError {
376    fn from(e: ParseIntError) -> Self { Self::ParseInt(e) }
377}
378
379/// Error returned when a hex string is missing a prefix (e.g. `0x`).
380#[derive(Debug, Clone, Eq, PartialEq)]
381pub struct MissingPrefixError {
382    hex: String,
383}
384
385impl MissingPrefixError {
386    /// Creates an error from the string with the missing prefix.
387    pub(crate) fn new(hex: String) -> Self { Self { hex } }
388}
389
390impl fmt::Display for MissingPrefixError {
391    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
392        write!(f, "hex string is missing a prefix (e.g. 0x): {}", self.hex)
393    }
394}
395
396#[cfg(feature = "std")]
397impl std::error::Error for MissingPrefixError {}
398
399/// Error when hex string contains a prefix (e.g. 0x).
400#[derive(Debug, Clone, Eq, PartialEq)]
401pub struct ContainsPrefixError {
402    hex: String,
403}
404
405impl ContainsPrefixError {
406    /// Creates an error from the string that contains the prefix.
407    pub(crate) fn new(hex: String) -> Self { Self { hex } }
408}
409
410impl fmt::Display for ContainsPrefixError {
411    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
412        write!(f, "hex string contains a prefix: {}", self.hex)
413    }
414}
415
416#[cfg(feature = "std")]
417impl std::error::Error for ContainsPrefixError {}
418
419#[cfg(test)]
420mod tests {
421    use super::*;
422
423    #[test]
424    fn parse_u32_from_hex_prefixed() {
425        let want = 171;
426        let got = hex_u32("0xab").expect("failed to parse prefixed hex");
427        assert_eq!(got, want);
428    }
429
430    #[test]
431    fn parse_u32_from_hex_no_prefix() {
432        let want = 171;
433        let got = hex_u32("ab").expect("failed to parse non-prefixed hex");
434        assert_eq!(got, want);
435    }
436
437    #[test]
438    fn parse_u128_from_hex_prefixed() {
439        let want = 3735928559;
440        let got = hex_u128("0xdeadbeef").expect("failed to parse prefixed hex");
441        assert_eq!(got, want);
442    }
443
444    #[test]
445    fn parse_u128_from_hex_no_prefix() {
446        let want = 3735928559;
447        let got = hex_u128("deadbeef").expect("failed to parse non-prefixed hex");
448        assert_eq!(got, want);
449    }
450
451    #[test]
452    fn parse_u32_from_hex_unchecked_errors_on_prefix() {
453        assert!(hex_u32_unchecked("0xab").is_err());
454    }
455}