dust_devil_core/
buffer_size.rs

1//! Provides the [`parse_pretty_buffer_size`] function, used to parse human-readable byte counts.
2
3use std::num::IntErrorKind;
4
5/// An error from parsing an invalid string with [`parse_pretty_buffer_size`].
6pub enum PrettyBufferSizeParseError {
7    /// The string is empty or blank.
8    Empty,
9
10    /// The string represents the value 0.
11    Zero,
12
13    /// The string doesn't follow a valid format.
14    InvalidFormat,
15
16    /// The string contains invalid characters.
17    InvalidCharacters,
18
19    /// The string represents a value equal to or over the 4GB limit.
20    TooLarge,
21}
22
23/// Parses a human-readable byte count that is not zero and not greater than [`u32::MAX`].
24///
25/// If a user wanted to specify a buffer size of 3 megabytes, then it'd be a pain in the ass for
26/// them to have to calculate and write `3145728`. So instead, this function allows parsing pretty
27/// values, like `3072K` or `3M` (or `3072KB` or `3MB`).
28///
29/// The available suffixes are `K` (kilo), `M` (mega) and `G` (giga). They are case-insensitive and may
30/// optionally be followed by a `b`/`B` character. Numbers can be specified in hexadecimal,
31/// octal or binary by using the (also case-insensitive) prefixes `0x`, `0o` and `0b` respectively.
32///
33/// Decimal numbers (e.g. `1.5M` or `16.0KB`) are not allowed.
34pub fn parse_pretty_buffer_size(s: &str) -> Result<u32, PrettyBufferSizeParseError> {
35    let s = s.trim();
36
37    if s.is_empty() {
38        return Err(PrettyBufferSizeParseError::Empty);
39    }
40
41    let mut iter = s.chars();
42    let (s, radix) = match (iter.next(), iter.next().map(|c| c.to_ascii_lowercase())) {
43        (Some('0'), Some('x')) => (&s[2..], 16),
44        (Some('0'), Some('o')) => (&s[2..], 8),
45        (Some('0'), Some('b')) => (&s[2..], 2),
46        _ => (s, 10),
47    };
48
49    let mut iter = s.chars();
50    let (s, multiplier) = match iter.next_back().map(|c| c.to_ascii_lowercase()) {
51        Some('k') => (&s[..(s.len() - 1)], 1024),
52        Some('m') => (&s[..(s.len() - 1)], 1024 * 1024),
53        Some('g') => (&s[..(s.len() - 1)], 1024 * 1024 * 1024),
54        Some('b') => match iter.next_back().map(|c| c.to_ascii_lowercase()) {
55            Some('k') => (&s[..(s.len() - 2)], 1024),
56            Some('m') => (&s[..(s.len() - 2)], 1024 * 1024),
57            Some('g') => (&s[..(s.len() - 2)], 1024 * 1024 * 1024),
58            Some(_) if radix < 11 => (&s[..(s.len() - 1)], 1),
59            _ => (s, 1),
60        },
61        _ => (s, 1),
62    };
63
64    match s.chars().next() {
65        Some(c) if c.is_ascii_alphanumeric() => {}
66        _ => return Err(PrettyBufferSizeParseError::InvalidFormat),
67    }
68
69    let size = match u32::from_str_radix(s, radix) {
70        Ok(0) => return Err(PrettyBufferSizeParseError::Zero),
71        Ok(size) => size,
72        Err(parse_int_error) => {
73            return Err(match parse_int_error.kind() {
74                IntErrorKind::Empty => PrettyBufferSizeParseError::Empty,
75                IntErrorKind::PosOverflow => PrettyBufferSizeParseError::TooLarge,
76                _ => PrettyBufferSizeParseError::InvalidCharacters,
77            });
78        }
79    };
80
81    match size.checked_mul(multiplier) {
82        Some(size) => Ok(size),
83        None => Err(PrettyBufferSizeParseError::TooLarge),
84    }
85}