pub unsafe fn strtonum<T>(
nptr: *const u8,
minval: T,
maxval: T,
) -> Result<T, &'static core::ffi::CStr>
where
T: Into<i64>,
i64: TryInto<T>,
{
let minval: i64 = minval.into();
let maxval: i64 = maxval.into();
if minval > maxval {
return Err(c"invalid");
}
let buf = unsafe { std::slice::from_raw_parts(nptr, crate::libc::strlen(nptr)) };
let s = std::str::from_utf8(buf).map_err(|_| c"invalid")?;
let n = s.trim_start().parse::<i64>().map_err(|_| c"invalid")?;
if n < minval {
return Err(c"too small");
}
if n > maxval {
return Err(c"too large");
}
match n.try_into() {
Ok(value) => Ok(value),
Err(_) => unreachable!("range check above should prevent this case"),
}
}
pub fn strtonum_<T>(s: &str, minval: T, maxval: T) -> Result<T, &'static core::ffi::CStr>
where
T: Into<i64>,
i64: TryInto<T>,
{
let minval: i64 = minval.into();
let maxval: i64 = maxval.into();
if minval > maxval {
return Err(c"invalid");
}
let n = s.trim_start().parse::<i64>().map_err(|_| c"invalid")?;
if n < minval {
return Err(c"too small");
}
if n > maxval {
return Err(c"too large");
}
match n.try_into() {
Ok(value) => Ok(value),
Err(_) => unreachable!("range check above should prevent this case"),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_strtonum_valid() {
assert_eq!(strtonum_("42", 0i32, 100i32), Ok(42));
}
#[test]
fn test_strtonum_leading_whitespace() {
assert_eq!(strtonum_(" 7", 0i32, 100i32), Ok(7));
}
#[test]
fn test_strtonum_too_small() {
assert_eq!(strtonum_("-5", 0i32, 100i32), Err(c"too small"));
}
#[test]
fn test_strtonum_too_large() {
assert_eq!(strtonum_("200", 0i32, 100i32), Err(c"too large"));
}
#[test]
fn test_strtonum_non_numeric() {
assert_eq!(strtonum_("abc", 0i32, 100i32), Err(c"invalid"));
}
#[test]
fn test_strtonum_inverted_range() {
assert_eq!(strtonum_("5", 100i32, 0i32), Err(c"invalid"));
}
}