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"));
}
#[test]
fn test_strtonum_inclusive_boundaries() {
assert_eq!(strtonum_("100", 0i32, 100i32), Ok(100));
assert_eq!(strtonum_("0", 0i32, 100i32), Ok(0));
assert_eq!(strtonum_("101", 0i32, 100i32), Err(c"too large"));
assert_eq!(strtonum_("-1", 0i32, 100i32), Err(c"too small"));
}
#[test]
fn test_strtonum_single_point_range() {
assert_eq!(strtonum_("5", 5i32, 5i32), Ok(5));
assert_eq!(strtonum_("6", 5i32, 5i32), Err(c"too large"));
assert_eq!(strtonum_("4", 5i32, 5i32), Err(c"too small"));
}
#[test]
fn test_strtonum_negative_range() {
assert_eq!(strtonum_("-50", -100i32, 100i32), Ok(-50));
assert_eq!(strtonum_("-100", -100i32, 100i32), Ok(-100));
assert_eq!(strtonum_("-101", -100i32, 100i32), Err(c"too small"));
}
#[test]
fn test_strtonum_leading_plus() {
assert_eq!(strtonum_("+42", 0i32, 100i32), Ok(42));
}
#[test]
fn test_strtonum_trailing_garbage() {
assert_eq!(strtonum_("42x", 0i32, 100i32), Err(c"invalid"));
assert_eq!(strtonum_("42 ", 0i32, 100i32), Err(c"invalid"));
assert_eq!(strtonum_("4 2", 0i32, 100i32), Err(c"invalid"));
}
#[test]
fn test_strtonum_empty_string() {
assert_eq!(strtonum_("", 0i32, 100i32), Err(c"invalid"));
assert_eq!(strtonum_(" ", 0i32, 100i32), Err(c"invalid"));
}
#[test]
fn test_strtonum_hex_not_accepted() {
assert_eq!(strtonum_("0x1f", 0i32, 100i32), Err(c"invalid"));
}
#[test]
fn test_strtonum_full_i64_range() {
assert_eq!(strtonum_("9223372036854775807", i64::MIN, i64::MAX), Ok(i64::MAX));
assert_eq!(strtonum_("-9223372036854775808", i64::MIN, i64::MAX), Ok(i64::MIN));
}
#[test]
fn test_strtonum_overflow_beyond_i64() {
assert_eq!(strtonum_("99999999999999999999999", i64::MIN, i64::MAX), Err(c"invalid"));
}
#[test]
fn test_strtonum_ptr_variant() {
unsafe {
assert_eq!(strtonum(crate::c!("55"), 0i32, 100i32), Ok(55));
assert_eq!(strtonum(crate::c!("999"), 0i32, 100i32), Err(c"too large"));
assert_eq!(strtonum(crate::c!("nope"), 0i32, 100i32), Err(c"invalid"));
}
}
}