Coverage Report

Created: 2026-09-28 07:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/jiff-0.2.35/src/util/parse.rs
Line
Count
Source
1
use jcore::bounds::Bounds;
2
3
use crate::{
4
    error::util::{ParseFractionError, ParseIntError},
5
    Error,
6
};
7
8
/// Parses an `i64` number from the beginning to the end of the given slice of
9
/// ASCII digit characters.
10
///
11
/// If any byte in the given slice is not `[0-9]`, then this returns an error.
12
/// Similarly, if the number parsed does not fit into a `i64`, then this
13
/// returns an error. Notably, this routine does not permit parsing a negative
14
/// integer. (We use `i64` because everything in this crate uses signed
15
/// integers, and because a higher level routine might want to parse the sign
16
/// and then apply it to the result of this routine.)
17
#[cfg_attr(feature = "perf-inline", inline(always))]
18
0
pub(crate) fn i64(bytes: &[u8]) -> Result<i64, ParseIntError> {
19
0
    if bytes.is_empty() {
20
0
        return Err(ParseIntError::NoDigitsFound);
21
0
    }
22
0
    let mut n: i64 = 0;
23
0
    for &byte in bytes {
24
0
        if !(b'0' <= byte && byte <= b'9') {
25
0
            return Err(ParseIntError::InvalidDigit(byte));
26
0
        }
27
0
        let digit = i64::from(byte - b'0');
28
0
        n = n
29
0
            .checked_mul(10)
30
0
            .and_then(|n| n.checked_add(digit))
31
0
            .ok_or(ParseIntError::TooBig)?;
32
    }
33
0
    Ok(n)
34
0
}
35
36
/// Like `self::i64`, but also does a boundary check for the given type.
37
///
38
/// # Errors
39
///
40
/// If the given slice is not a valid integer (i.e., overflow or contains
41
/// anything other than `[0-9]`) or is not in the bounds for the given `Bounds`
42
/// implementation, then an error is returned.
43
///
44
/// Note that the error can either be a parsing error or it can be a
45
/// boundary error.
46
#[cfg_attr(feature = "perf-inline", inline(always))]
47
0
pub(crate) fn bi64<B>(bytes: &[u8]) -> Result<B::Primitive, Error>
48
0
where
49
0
    B: Bounds,
50
0
    Error: From<B::Error>,
51
{
52
0
    Ok(B::check(self::i64(bytes)?)?)
53
0
}
Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::LeapSecond>
Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::OffsetHours>
Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::OffsetMinutes>
Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::Day>
Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::Hour>
Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::Year>
Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::Minute>
Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::Month>
Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::Year>
Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::OffsetHours>
Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::OffsetMinutes>
Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::OffsetSeconds>
Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::LeapSecond>
Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::WeekdayMondayOne>
Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::Day>
Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::Hour>
Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::Month>
Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::Minute>
Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::ISOWeek>
54
55
/// Parsed an optional `u64` that is a prefix of `bytes`.
56
///
57
/// If no digits (`[0-9]`) were found at the beginning of `bytes`, then `None`
58
/// is returned.
59
///
60
/// Note that this is safe to call on untrusted input. It will not attempt
61
/// to consume more input than could possibly fit into a parsed integer.
62
///
63
/// Since this returns a `u64`, it is possible that an integer that cannot
64
/// fit into an `i64` is returned. Callers should handle this. (Indeed,
65
/// `DurationUnits` handles this case.)
66
///
67
/// # Errors
68
///
69
/// When the parsed integer cannot fit into a `u64`.
70
#[cfg_attr(feature = "perf-inline", inline(always))]
71
0
pub(crate) fn u64_prefix(
72
0
    bytes: &[u8],
73
0
) -> Result<(Option<u64>, &[u8]), ParseIntError> {
74
    // Discovered via `u64::MAX.to_string().len()`.
75
    const MAX_U64_DIGITS: usize = 20;
76
77
0
    let mut digit_count = 0;
78
0
    let mut n: u64 = 0;
79
0
    while digit_count <= MAX_U64_DIGITS {
80
0
        let Some(&byte) = bytes.get(digit_count) else { break };
81
0
        if !byte.is_ascii_digit() {
82
0
            break;
83
0
        }
84
0
        digit_count += 1;
85
        // OK because we confirmed `byte` is an ASCII digit.
86
0
        let digit = u64::from(byte - b'0');
87
0
        n = n
88
0
            .checked_mul(10)
89
0
            .and_then(|n| n.checked_add(digit))
90
0
            .ok_or(ParseIntError::TooBig)?;
91
    }
92
0
    if digit_count == 0 {
93
0
        return Ok((None, bytes));
94
0
    }
95
0
    Ok((Some(n), &bytes[digit_count..]))
96
0
}
97
98
/// Parses a `u32` fractional number from the beginning to the end of the given
99
/// slice of ASCII digit characters.
100
///
101
/// The fraction's maximum precision is always 9 digits. The returned integer
102
/// will always be in units of `10^{max_precision}`. For example, this
103
/// will parse a fractional amount of seconds with a maximum precision of
104
/// nanoseconds.
105
///
106
/// If any byte in the given slice is not `[0-9]`, then this returns an error.
107
/// Notably, this routine does not permit parsing a negative integer.
108
0
pub(crate) fn fraction(bytes: &[u8]) -> Result<u32, ParseFractionError> {
109
0
    if bytes.is_empty() {
110
0
        return Err(ParseFractionError::NoDigitsFound);
111
0
    } else if bytes.len() > ParseFractionError::MAX_PRECISION {
112
0
        return Err(ParseFractionError::TooManyDigits);
113
0
    }
114
0
    let mut n: u32 = 0;
115
0
    for &byte in bytes {
116
0
        let digit = match byte.checked_sub(b'0') {
117
            None => {
118
0
                return Err(ParseFractionError::InvalidDigit(byte));
119
            }
120
0
            Some(digit) if digit > 9 => {
121
0
                return Err(ParseFractionError::InvalidDigit(byte));
122
            }
123
0
            Some(digit) => {
124
0
                debug_assert!((0..=9).contains(&digit));
125
0
                u32::from(digit)
126
            }
127
        };
128
0
        n = n
129
0
            .checked_mul(10)
130
0
            .and_then(|n| n.checked_add(digit))
131
0
            .ok_or_else(|| ParseFractionError::TooBig)?;
132
    }
133
0
    for _ in bytes.len()..ParseFractionError::MAX_PRECISION {
134
0
        n = n.checked_mul(10).ok_or_else(|| ParseFractionError::TooBig)?;
135
    }
136
0
    Ok(n)
137
0
}
138
139
/// Parses an `OsStr` into a `&str` when `&[u8]` isn't easily available.
140
///
141
/// This is effectively `OsStr::to_str`, but with a slightly better error
142
/// message.
143
#[cfg(any(feature = "tz-system", feature = "tzdb-zoneinfo"))]
144
4.84k
pub(crate) fn os_str_utf8<'o, O>(
145
4.84k
    os_str: &'o O,
146
4.84k
) -> Result<&'o str, crate::error::util::OsStrUtf8Error>
147
4.84k
where
148
4.84k
    O: ?Sized + AsRef<std::ffi::OsStr>,
149
{
150
4.84k
    let os_str = os_str.as_ref();
151
4.84k
    os_str
152
4.84k
        .to_str()
153
4.84k
        .ok_or_else(|| crate::error::util::OsStrUtf8Error::from(os_str))
154
4.84k
}
155
156
/// Splits the given input into two slices at the given position.
157
///
158
/// If the position is greater than the length of the slice given, then this
159
/// returns `None`.
160
#[cfg_attr(feature = "perf-inline", inline(always))]
161
0
pub(crate) fn split(input: &[u8], at: usize) -> Option<(&[u8], &[u8])> {
162
0
    if at > input.len() {
163
0
        None
164
    } else {
165
0
        Some(input.split_at(at))
166
    }
167
0
}
168
169
/// Returns a function that converts two slices to an offset.
170
///
171
/// It takes the starting point as input and returns a function that, when
172
/// given an ending point (greater than or equal to the starting point), then
173
/// the corresponding pointers are subtracted and an offset relative to the
174
/// starting point is returned.
175
///
176
/// This is useful as a helper function in parsing routines that use slices
177
/// but want to report offsets.
178
///
179
/// # Panics
180
///
181
/// This may panic if the ending point is not a suffix slice of `start`.
182
0
pub(crate) fn offseter<'a>(
183
0
    start: &'a [u8],
184
0
) -> impl Fn(&'a [u8]) -> usize + 'a {
185
0
    move |end| (end.as_ptr() as usize) - (start.as_ptr() as usize)
186
0
}
187
188
/// Returns a function that converts two slices to the slice between them.
189
///
190
/// This takes a starting point as input and returns a function that, when
191
/// given an ending point (greater than or equal to the starting point), it
192
/// returns a slice beginning at the starting point and ending just at the
193
/// ending point.
194
///
195
/// This is useful as a helper function in parsing routines.
196
///
197
/// # Panics
198
///
199
/// This may panic if the ending point is not a suffix slice of `start`.
200
0
pub(crate) fn slicer<'a>(
201
0
    start: &'a [u8],
202
0
) -> impl Fn(&'a [u8]) -> &'a [u8] + 'a {
203
0
    let mkoffset = offseter(start);
204
0
    move |end| {
205
0
        let offset = mkoffset(end);
206
0
        &start[..offset]
207
0
    }
208
0
}