ztmux 0.1.0

A Rust port of tmux — the full terminal multiplexer, server and client
Documentation
/// C `vendor/tmux/compat/strtonum.c:31`: `long long strtonum(const char *numstr, long long minval, long long maxval, const char **errstrp)`
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() {
        // trim_start allows leading spaces.
        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() {
        // minval > maxval is rejected before parsing.
        assert_eq!(strtonum_("5", 100i32, 0i32), Err(c"invalid"));
    }
}