1use std::ffi::{c_char, CStr, NulError};
9
10use llama_cpp_sys_4 as sys;
11
12use crate::token::LlamaToken;
13
14#[derive(Debug, thiserror::Error)]
20pub enum ShimError {
21 #[error("argument contained an interior NUL byte")]
25 Nul(#[from] NulError),
26 #[error("invalid argument passed to the shim")]
28 InvalidArg,
29 #[error("input was not valid JSON: {0}")]
31 BadJson(String),
32 #[error("llama.cpp call failed: {0}")]
34 Failed(String),
35 #[error("could not construct: {0}")]
37 Init(String),
38 #[error("llama.cpp returned non-UTF-8 output")]
40 Utf8(#[from] std::string::FromUtf8Error),
41 #[error("shim reported an out-of-range offset")]
44 CorruptResult,
45}
46
47pub(crate) type Result<T> = std::result::Result<T, ShimError>;
48
49pub(crate) fn last_error() -> String {
53 let ptr = unsafe { sys::llama_shim_last_error() };
54 if ptr.is_null() {
55 return String::new();
56 }
57 unsafe { CStr::from_ptr(ptr) }
58 .to_string_lossy()
59 .into_owned()
60}
61
62pub(crate) fn check_status(status: i32) -> Result<()> {
64 match status {
65 sys::LLAMA_SHIM_OK => Ok(()),
66 sys::LLAMA_SHIM_INVALID_ARG => Err(ShimError::InvalidArg),
67 sys::LLAMA_SHIM_BAD_JSON => Err(ShimError::BadJson(last_error())),
68 _ => Err(ShimError::Failed(last_error())),
69 }
70}
71
72pub(crate) fn read_string<F>(mut call: F) -> Result<String>
77where
78 F: FnMut(*mut c_char, usize, *mut usize) -> i32,
79{
80 let mut needed: usize = 0;
81 let status = call(std::ptr::null_mut(), 0, &raw mut needed);
82 if status != sys::LLAMA_SHIM_BUFFER_TOO_SMALL {
83 check_status(status)?;
84 }
85 if needed == 0 {
86 return Ok(String::new());
87 }
88
89 let mut buf = vec![0u8; needed];
90 let status = call(
91 buf.as_mut_ptr().cast::<c_char>(),
92 buf.len(),
93 &raw mut needed,
94 );
95 check_status(status)?;
96
97 let end = buf.iter().position(|b| *b == 0).unwrap_or(buf.len());
100 buf.truncate(end);
101 String::from_utf8(buf).map_err(ShimError::from)
102}
103
104pub(crate) fn read_tokens<F>(mut call: F) -> Result<Vec<LlamaToken>>
106where
107 F: FnMut(*mut i32, usize, *mut usize) -> i32,
108{
109 let mut needed: usize = 0;
110 let status = call(std::ptr::null_mut(), 0, &raw mut needed);
111 if status != sys::LLAMA_SHIM_BUFFER_TOO_SMALL {
112 check_status(status)?;
113 }
114 if needed == 0 {
115 return Ok(Vec::new());
116 }
117
118 let mut buf = vec![0i32; needed];
119 let status = call(buf.as_mut_ptr(), buf.len(), &raw mut needed);
120 check_status(status)?;
121 buf.truncate(needed);
122 Ok(buf.into_iter().map(LlamaToken).collect())
123}
124
125pub(crate) fn read_i32s<F>(mut call: F) -> Result<Vec<i32>>
128where
129 F: FnMut(*mut i32, usize, *mut usize) -> i32,
130{
131 let mut needed: usize = 0;
132 let status = call(std::ptr::null_mut(), 0, &raw mut needed);
133 if status != sys::LLAMA_SHIM_BUFFER_TOO_SMALL {
134 check_status(status)?;
135 }
136 if needed == 0 {
137 return Ok(Vec::new());
138 }
139
140 let mut buf = vec![0i32; needed];
141 let status = call(buf.as_mut_ptr(), buf.len(), &raw mut needed);
142 check_status(status)?;
143 buf.truncate(needed);
144 Ok(buf)
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
155 fn status_codes_are_distinct_and_signed_as_documented() {
156 assert_eq!(sys::LLAMA_SHIM_OK, 0);
159 assert_eq!(
160 sys::LLAMA_SHIM_BUFFER_TOO_SMALL,
161 1,
162 "a size query is not a failure, so it must be positive"
163 );
164 assert_eq!(sys::LLAMA_SHIM_INVALID_ARG, -1);
165 assert_eq!(sys::LLAMA_SHIM_BAD_JSON, -2);
166 assert_eq!(sys::LLAMA_SHIM_THROWN, -3);
167 }
168
169 #[test]
170 fn check_status_maps_each_code() {
171 assert!(check_status(sys::LLAMA_SHIM_OK).is_ok());
172 assert!(matches!(
173 check_status(sys::LLAMA_SHIM_INVALID_ARG),
174 Err(ShimError::InvalidArg)
175 ));
176 assert!(matches!(
177 check_status(sys::LLAMA_SHIM_BAD_JSON),
178 Err(ShimError::BadJson(_))
179 ));
180 assert!(matches!(
181 check_status(sys::LLAMA_SHIM_THROWN),
182 Err(ShimError::Failed(_))
183 ));
184 }
185
186 #[test]
189 fn last_error_is_empty_before_any_failure() {
190 let _ = last_error();
193 }
194}