use std::ffi::{CStr, CString, NulError};
use std::path::{Path, PathBuf};
use std::ptr::NonNull;
#[derive(Debug, thiserror::Error)]
pub enum GgufError {
#[error("gguf path contained an interior NUL byte")]
Nul(#[from] NulError),
#[error("gguf path was not valid UTF-8")]
InvalidPath,
#[error("failed to open or parse GGUF file: {0}")]
Init(PathBuf),
}
#[derive(Debug)]
pub struct GgufContext {
ctx: NonNull<ik_llama_cpp_sys::gguf_context>,
}
impl GgufContext {
pub fn from_file(path: &Path) -> Result<Self, GgufError> {
let path_str = path.to_str().ok_or(GgufError::InvalidPath)?;
let c_path = CString::new(path_str)?;
let params = ik_llama_cpp_sys::gguf_init_params {
no_alloc: true,
ctx: std::ptr::null_mut(),
};
let ptr = unsafe { ik_llama_cpp_sys::gguf_init_from_file(c_path.as_ptr(), params) };
let ctx = NonNull::new(ptr).ok_or_else(|| GgufError::Init(path.to_path_buf()))?;
Ok(Self { ctx })
}
#[must_use]
pub fn version(&self) -> i32 {
unsafe { ik_llama_cpp_sys::gguf_get_version(self.ctx.as_ptr()) }
}
#[must_use]
pub fn alignment(&self) -> usize {
unsafe { ik_llama_cpp_sys::gguf_get_alignment(self.ctx.as_ptr()) }
}
#[must_use]
pub fn n_kv(&self) -> i32 {
unsafe { ik_llama_cpp_sys::gguf_get_n_kv(self.ctx.as_ptr()) }
}
#[must_use]
pub fn find_key(&self, key: &str) -> i32 {
let Ok(c_key) = CString::new(key) else {
return -1;
};
unsafe { ik_llama_cpp_sys::gguf_find_key(self.ctx.as_ptr(), c_key.as_ptr()) }
}
#[must_use]
pub fn key_at(&self, idx: i32) -> Option<&str> {
let ptr = unsafe { ik_llama_cpp_sys::gguf_get_key(self.ctx.as_ptr(), idx) };
if ptr.is_null() {
return None;
}
unsafe { CStr::from_ptr(ptr).to_str().ok() }
}
#[must_use]
pub fn kv_type(&self, idx: i32) -> ik_llama_cpp_sys::gguf_type {
unsafe { ik_llama_cpp_sys::gguf_get_kv_type(self.ctx.as_ptr(), idx) }
}
#[must_use]
pub fn arr_type(&self, idx: i32) -> ik_llama_cpp_sys::gguf_type {
unsafe { ik_llama_cpp_sys::gguf_get_arr_type(self.ctx.as_ptr(), idx) }
}
#[must_use]
pub fn val_u8(&self, idx: i32) -> u8 {
unsafe { ik_llama_cpp_sys::gguf_get_val_u8(self.ctx.as_ptr(), idx) }
}
#[must_use]
pub fn val_i8(&self, idx: i32) -> i8 {
unsafe { ik_llama_cpp_sys::gguf_get_val_i8(self.ctx.as_ptr(), idx) }
}
#[must_use]
pub fn val_u16(&self, idx: i32) -> u16 {
unsafe { ik_llama_cpp_sys::gguf_get_val_u16(self.ctx.as_ptr(), idx) }
}
#[must_use]
pub fn val_i16(&self, idx: i32) -> i16 {
unsafe { ik_llama_cpp_sys::gguf_get_val_i16(self.ctx.as_ptr(), idx) }
}
#[must_use]
pub fn val_u32(&self, idx: i32) -> u32 {
unsafe { ik_llama_cpp_sys::gguf_get_val_u32(self.ctx.as_ptr(), idx) }
}
#[must_use]
pub fn val_i32(&self, idx: i32) -> i32 {
unsafe { ik_llama_cpp_sys::gguf_get_val_i32(self.ctx.as_ptr(), idx) }
}
#[must_use]
pub fn val_f32(&self, idx: i32) -> f32 {
unsafe { ik_llama_cpp_sys::gguf_get_val_f32(self.ctx.as_ptr(), idx) }
}
#[must_use]
pub fn val_u64(&self, idx: i32) -> u64 {
unsafe { ik_llama_cpp_sys::gguf_get_val_u64(self.ctx.as_ptr(), idx) }
}
#[must_use]
pub fn val_i64(&self, idx: i32) -> i64 {
unsafe { ik_llama_cpp_sys::gguf_get_val_i64(self.ctx.as_ptr(), idx) }
}
#[must_use]
pub fn val_f64(&self, idx: i32) -> f64 {
unsafe { ik_llama_cpp_sys::gguf_get_val_f64(self.ctx.as_ptr(), idx) }
}
#[must_use]
pub fn val_bool(&self, idx: i32) -> bool {
unsafe { ik_llama_cpp_sys::gguf_get_val_bool(self.ctx.as_ptr(), idx) }
}
#[must_use]
pub fn val_str(&self, idx: i32) -> Option<&str> {
let ptr = unsafe { ik_llama_cpp_sys::gguf_get_val_str(self.ctx.as_ptr(), idx) };
if ptr.is_null() {
return None;
}
unsafe { CStr::from_ptr(ptr).to_str().ok() }
}
#[must_use]
pub fn arr_len(&self, idx: i32) -> i32 {
unsafe { ik_llama_cpp_sys::gguf_get_arr_n(self.ctx.as_ptr(), idx) }
}
#[must_use]
pub fn arr_str(&self, idx: i32, i: i32) -> Option<&str> {
if i < 0 || i >= self.arr_len(idx) {
return None;
}
let ptr = unsafe { ik_llama_cpp_sys::gguf_get_arr_str(self.ctx.as_ptr(), idx, i) };
if ptr.is_null() {
return None;
}
unsafe { CStr::from_ptr(ptr).to_str().ok() }
}
#[must_use]
pub fn n_tensors(&self) -> i32 {
unsafe { ik_llama_cpp_sys::gguf_get_n_tensors(self.ctx.as_ptr()) }
}
#[must_use]
pub fn tensor_name(&self, i: i32) -> Option<&str> {
if i < 0 || i >= self.n_tensors() {
return None;
}
let ptr = unsafe { ik_llama_cpp_sys::gguf_get_tensor_name(self.ctx.as_ptr(), i) };
if ptr.is_null() {
return None;
}
unsafe { CStr::from_ptr(ptr).to_str().ok() }
}
#[must_use]
pub fn tensor_type(&self, i: i32) -> Option<ik_llama_cpp_sys::ggml_type> {
if i < 0 || i >= self.n_tensors() {
return None;
}
Some(unsafe { ik_llama_cpp_sys::gguf_get_tensor_type(self.ctx.as_ptr(), i) })
}
#[must_use]
pub fn tensor_offset(&self, i: i32) -> Option<usize> {
if i < 0 || i >= self.n_tensors() {
return None;
}
Some(unsafe { ik_llama_cpp_sys::gguf_get_tensor_offset(self.ctx.as_ptr(), i) })
}
}
#[must_use]
pub fn type_name(kind: ik_llama_cpp_sys::gguf_type) -> Option<&'static str> {
let ptr = unsafe { ik_llama_cpp_sys::gguf_type_name(kind) };
if ptr.is_null() {
return None;
}
unsafe { CStr::from_ptr(ptr).to_str().ok() }
}
impl Drop for GgufContext {
fn drop(&mut self) {
unsafe { ik_llama_cpp_sys::gguf_free(self.ctx.as_ptr()) }
}
}
#[cfg(all(test, feature = "_smoke"))]
mod tests {
use super::*;
#[test]
fn reads_architecture() {
let Ok(model) = std::env::var("IK_TEST_MODEL") else {
eprintln!("IK_TEST_MODEL not set; skipping gguf smoke test");
return;
};
let ctx = GgufContext::from_file(Path::new(&model)).expect("open gguf");
assert!(ctx.n_kv() > 0, "expected at least one KV pair");
let idx = ctx.find_key("general.architecture");
assert!(idx >= 0, "general.architecture key missing");
assert_eq!(ctx.val_str(idx), Some("qwen35"));
}
}