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(|e| match e.kind() {
core::num::IntErrorKind::PosOverflow => c"too large",
core::num::IntErrorKind::NegOverflow => c"too small",
_ => 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(|e| match e.kind() {
core::num::IntErrorKind::PosOverflow => c"too large",
core::num::IntErrorKind::NegOverflow => c"too small",
_ => 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"too large"));
}
#[test]
fn test_strtonum_overflow_positive_small_max() {
assert_eq!(strtonum_("99999999999999999999", 0i32, 100i32), Err(c"too large"));
}
#[test]
fn test_strtonum_overflow_negative_small_max() {
assert_eq!(strtonum_("-99999999999999999999", 0i32, 100i32), Err(c"too small"));
}
#[test]
fn test_strtonum_overflow_garbage_still_invalid() {
assert_eq!(strtonum_("abc", 0i32, 100i32), Err(c"invalid"));
}
#[test]
fn test_strtonum_ptr_overflow() {
unsafe {
assert_eq!(
strtonum(crate::c!("99999999999999999999"), i64::MIN, i64::MAX),
Err(c"too large")
);
assert_eq!(
strtonum(crate::c!("-99999999999999999999"), i64::MIN, i64::MAX),
Err(c"too small")
);
}
}
#[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"));
}
}
#[test]
fn test_strtonum_u8_narrowing() {
assert_eq!(strtonum_("255", 0u8, 255u8), Ok(255u8));
assert_eq!(strtonum_("0", 0u8, 255u8), Ok(0u8));
assert_eq!(strtonum_("256", 0u8, 255u8), Err(c"too large"));
assert_eq!(strtonum_("200", 0u8, 100u8), Err(c"too large"));
}
#[test]
fn test_strtonum_i16_window() {
assert_eq!(strtonum_("-100", -100i16, 100i16), Ok(-100i16));
assert_eq!(strtonum_("100", -100i16, 100i16), Ok(100i16));
assert_eq!(strtonum_("-101", -100i16, 100i16), Err(c"too small"));
assert_eq!(strtonum_("101", -100i16, 100i16), Err(c"too large"));
}
#[test]
fn test_strtonum_negative_zero() {
assert_eq!(strtonum_("-0", 0i32, 100i32), Ok(0));
assert_eq!(strtonum_("+0", 0i32, 100i32), Ok(0));
}
#[test]
fn test_strtonum_lone_sign_invalid() {
assert_eq!(strtonum_("-", 0i32, 100i32), Err(c"invalid"));
assert_eq!(strtonum_("+", 0i32, 100i32), Err(c"invalid"));
assert_eq!(strtonum_(" -", 0i32, 100i32), Err(c"invalid"));
}
}