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() };
57 if ptr.is_null() {
58 return String::new();
59 }
60 unsafe { CStr::from_ptr(ptr) }
63 .to_string_lossy()
64 .into_owned()
65}
66
67pub(crate) fn check_status(status: i32) -> Result<()> {
69 match status {
70 sys::LLAMA_SHIM_OK => Ok(()),
71 sys::LLAMA_SHIM_INVALID_ARG => Err(ShimError::InvalidArg),
72 sys::LLAMA_SHIM_BAD_JSON => Err(ShimError::BadJson(last_error())),
73 _ => Err(ShimError::Failed(last_error())),
74 }
75}
76
77pub(crate) fn read_string<F>(mut call: F) -> Result<String>
82where
83 F: FnMut(*mut c_char, usize, *mut usize) -> i32,
84{
85 let mut needed: usize = 0;
86 let status = call(std::ptr::null_mut(), 0, &raw mut needed);
87 if status != sys::LLAMA_SHIM_BUFFER_TOO_SMALL {
88 check_status(status)?;
89 }
90 if needed == 0 {
91 return Ok(String::new());
92 }
93
94 let mut buf = vec![0u8; needed];
95 let status = call(
96 buf.as_mut_ptr().cast::<c_char>(),
97 buf.len(),
98 &raw mut needed,
99 );
100 check_status(status)?;
101
102 let end = buf.iter().position(|b| *b == 0).unwrap_or(buf.len());
105 buf.truncate(end);
106 String::from_utf8(buf).map_err(ShimError::from)
107}
108
109pub(crate) fn read_tokens<F>(mut call: F) -> Result<Vec<LlamaToken>>
111where
112 F: FnMut(*mut i32, usize, *mut usize) -> i32,
113{
114 let mut needed: usize = 0;
115 let status = call(std::ptr::null_mut(), 0, &raw mut needed);
116 if status != sys::LLAMA_SHIM_BUFFER_TOO_SMALL {
117 check_status(status)?;
118 }
119 if needed == 0 {
120 return Ok(Vec::new());
121 }
122
123 let mut buf = vec![0i32; needed];
124 let status = call(buf.as_mut_ptr(), buf.len(), &raw mut needed);
125 check_status(status)?;
126 buf.truncate(needed);
127 Ok(buf.into_iter().map(LlamaToken).collect())
128}
129
130pub(crate) fn read_i32s<F>(mut call: F) -> Result<Vec<i32>>
133where
134 F: FnMut(*mut i32, usize, *mut usize) -> i32,
135{
136 let mut needed: usize = 0;
137 let status = call(std::ptr::null_mut(), 0, &raw mut needed);
138 if status != sys::LLAMA_SHIM_BUFFER_TOO_SMALL {
139 check_status(status)?;
140 }
141 if needed == 0 {
142 return Ok(Vec::new());
143 }
144
145 let mut buf = vec![0i32; needed];
146 let status = call(buf.as_mut_ptr(), buf.len(), &raw mut needed);
147 check_status(status)?;
148 buf.truncate(needed);
149 Ok(buf)
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
160 fn status_codes_are_distinct_and_signed_as_documented() {
161 assert_eq!(sys::LLAMA_SHIM_OK, 0);
164 assert_eq!(
165 sys::LLAMA_SHIM_BUFFER_TOO_SMALL,
166 1,
167 "a size query is not a failure, so it must be positive"
168 );
169 assert_eq!(sys::LLAMA_SHIM_INVALID_ARG, -1);
170 assert_eq!(sys::LLAMA_SHIM_BAD_JSON, -2);
171 assert_eq!(sys::LLAMA_SHIM_THROWN, -3);
172 }
173
174 #[test]
175 fn check_status_maps_each_code() {
176 assert!(check_status(sys::LLAMA_SHIM_OK).is_ok());
177 assert!(matches!(
178 check_status(sys::LLAMA_SHIM_INVALID_ARG),
179 Err(ShimError::InvalidArg)
180 ));
181 assert!(matches!(
182 check_status(sys::LLAMA_SHIM_BAD_JSON),
183 Err(ShimError::BadJson(_))
184 ));
185 assert!(matches!(
186 check_status(sys::LLAMA_SHIM_THROWN),
187 Err(ShimError::Failed(_))
188 ));
189 }
190
191 #[test]
194 fn last_error_is_empty_before_any_failure() {
195 let _ = last_error();
198 }
199}