skippy-runtime 0.76.1

Rust runtime layer for Skippy staged model execution
use std::ffi::CString;
use std::path::Path;

use anyhow::{Context, Result};

#[cfg(unix)]
use std::os::unix::ffi::OsStrExt;

pub(crate) fn path_to_cstring(path: &Path, label: &str) -> Result<CString> {
    path_bytes(path, label).and_then(|bytes| {
        CString::new(bytes).with_context(|| format!("{label} contains an interior NUL byte"))
    })
}

#[cfg(unix)]
fn path_bytes(path: &Path, _label: &str) -> Result<Vec<u8>> {
    Ok(path.as_os_str().as_bytes().to_vec())
}

#[cfg(not(unix))]
fn path_bytes(path: &Path, label: &str) -> Result<Vec<u8>> {
    let path = path
        .to_str()
        .ok_or_else(|| anyhow::anyhow!("{label} is not valid UTF-8"))?;
    Ok(path.as_bytes().to_vec())
}

#[cfg(test)]
mod tests {
    use super::path_to_cstring;
    use std::path::PathBuf;

    #[cfg(any(unix, windows))]
    use std::ffi::OsString;
    #[cfg(unix)]
    use std::os::unix::ffi::OsStringExt;
    #[cfg(windows)]
    use std::os::windows::ffi::OsStringExt;

    #[test]
    #[cfg(unix)]
    fn path_to_cstring_preserves_unix_os_bytes() -> anyhow::Result<()> {
        let path = PathBuf::from(OsString::from_vec(vec![b'/', b't', b'm', b'p', b'/', 0xff]));

        let cstring = path_to_cstring(&path, "model path")?;

        assert_eq!(cstring.as_bytes(), b"/tmp/\xff");
        Ok(())
    }

    #[test]
    #[cfg(unix)]
    fn path_to_cstring_rejects_unix_interior_nul() {
        let path = PathBuf::from(OsString::from_vec(vec![b'/', b't', b'm', b'p', b'/', 0]));

        let error = path_to_cstring(&path, "model path")
            .unwrap_err()
            .to_string();

        assert!(
            error.contains("model path contains an interior NUL byte"),
            "unexpected error: {error}"
        );
    }

    #[test]
    #[cfg(not(unix))]
    fn path_to_cstring_accepts_utf8_paths_on_non_unix() -> anyhow::Result<()> {
        let path = PathBuf::from("C:/models/model.gguf");

        let cstring = path_to_cstring(&path, "model path")?;

        assert_eq!(cstring.as_bytes(), b"C:/models/model.gguf");
        Ok(())
    }

    #[test]
    #[cfg(windows)]
    fn path_to_cstring_rejects_windows_paths_that_are_not_utf8() {
        let path = PathBuf::from(OsString::from_wide(&[0xD800]));

        let error = path_to_cstring(&path, "model path")
            .unwrap_err()
            .to_string();

        assert!(
            error.contains("model path is not valid UTF-8"),
            "unexpected error: {error}"
        );
    }
}