1use std::ffi::{CStr, CString};
2use std::fmt;
3use std::os::raw::c_void;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum ErrorCode {
8 NotFound,
9 AlreadyExists,
10 InvalidArgument,
11 PermissionDenied,
12 FailedPrecondition,
13 ResourceExhausted,
14 Unavailable,
15 InternalError,
16 NotSupported,
17 Unknown,
18}
19
20impl From<u32> for ErrorCode {
21 fn from(code: u32) -> Self {
22 match code {
23 1 => ErrorCode::NotFound,
24 2 => ErrorCode::AlreadyExists,
25 3 => ErrorCode::InvalidArgument,
26 4 => ErrorCode::PermissionDenied,
27 5 => ErrorCode::FailedPrecondition,
28 6 => ErrorCode::ResourceExhausted,
29 7 => ErrorCode::Unavailable,
30 8 => ErrorCode::InternalError,
31 9 => ErrorCode::NotSupported,
32 _ => ErrorCode::Unknown,
33 }
34 }
35}
36
37impl From<ErrorCode> for u32 {
38 fn from(code: ErrorCode) -> Self {
39 match code {
40 ErrorCode::NotFound => 1,
41 ErrorCode::AlreadyExists => 2,
42 ErrorCode::InvalidArgument => 3,
43 ErrorCode::PermissionDenied => 4,
44 ErrorCode::FailedPrecondition => 5,
45 ErrorCode::ResourceExhausted => 6,
46 ErrorCode::Unavailable => 7,
47 ErrorCode::InternalError => 8,
48 ErrorCode::NotSupported => 9,
49 ErrorCode::Unknown => 10,
50 }
51 }
52}
53
54impl fmt::Display for ErrorCode {
55 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56 match self {
57 ErrorCode::NotFound => write!(f, "NotFound"),
58 ErrorCode::AlreadyExists => write!(f, "AlreadyExists"),
59 ErrorCode::InvalidArgument => write!(f, "InvalidArgument"),
60 ErrorCode::PermissionDenied => write!(f, "PermissionDenied"),
61 ErrorCode::FailedPrecondition => write!(f, "FailedPrecondition"),
62 ErrorCode::ResourceExhausted => write!(f, "ResourceExhausted"),
63 ErrorCode::Unavailable => write!(f, "Unavailable"),
64 ErrorCode::InternalError => write!(f, "InternalError"),
65 ErrorCode::NotSupported => write!(f, "NotSupported"),
66 ErrorCode::Unknown => write!(f, "Unknown"),
67 }
68 }
69}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
73pub struct Error {
74 pub code: ErrorCode,
75 pub message: String,
76}
77
78impl Error {
79 pub fn is_not_found(&self) -> bool {
81 self.code == ErrorCode::NotFound
82 }
83
84 pub fn is_already_exists(&self) -> bool {
86 self.code == ErrorCode::AlreadyExists
87 }
88
89 pub fn is_invalid_argument(&self) -> bool {
91 self.code == ErrorCode::InvalidArgument
92 }
93}
94
95impl fmt::Display for Error {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 write!(f, "zvec error {}: {}", self.code, self.message)
98 }
99}
100
101impl std::error::Error for Error {}
102
103pub type Result<T> = std::result::Result<T, Error>;
105
106pub(crate) fn check_error(code: zvec_rust_sys::zvec_error_code_t) -> Result<()> {
111 if code == zvec_rust_sys::ZVEC_OK {
112 return Ok(());
113 }
114
115 let message = unsafe {
116 let mut c_msg: *mut std::os::raw::c_char = std::ptr::null_mut();
117 zvec_rust_sys::zvec_get_last_error(&mut c_msg);
118
119 let msg = if c_msg.is_null() {
120 "unknown error".to_string()
121 } else {
122 let s = CStr::from_ptr(c_msg).to_string_lossy().into_owned();
123 zvec_rust_sys::zvec_free(c_msg as *mut c_void);
124 s
125 };
126 msg
127 };
128
129 Err(Error {
130 code: ErrorCode::from(code),
131 message,
132 })
133}
134
135pub(crate) fn to_cstring(s: &str) -> Result<CString> {
138 CString::new(s).map_err(|_| Error {
139 code: ErrorCode::InvalidArgument,
140 message: "string contains null byte".into(),
141 })
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn error_code_from_u32_known_values() {
150 assert_eq!(ErrorCode::from(1), ErrorCode::NotFound);
151 assert_eq!(ErrorCode::from(2), ErrorCode::AlreadyExists);
152 assert_eq!(ErrorCode::from(3), ErrorCode::InvalidArgument);
153 assert_eq!(ErrorCode::from(4), ErrorCode::PermissionDenied);
154 assert_eq!(ErrorCode::from(5), ErrorCode::FailedPrecondition);
155 assert_eq!(ErrorCode::from(6), ErrorCode::ResourceExhausted);
156 assert_eq!(ErrorCode::from(7), ErrorCode::Unavailable);
157 assert_eq!(ErrorCode::from(8), ErrorCode::InternalError);
158 assert_eq!(ErrorCode::from(9), ErrorCode::NotSupported);
159 }
160
161 #[test]
162 fn error_code_from_u32_unknown_falls_back() {
163 assert_eq!(ErrorCode::from(0), ErrorCode::Unknown);
164 assert_eq!(ErrorCode::from(10), ErrorCode::Unknown);
165 assert_eq!(ErrorCode::from(99), ErrorCode::Unknown);
166 assert_eq!(ErrorCode::from(u32::MAX), ErrorCode::Unknown);
167 }
168
169 #[test]
170 fn error_code_to_u32_roundtrip() {
171 let codes = [
172 ErrorCode::NotFound,
173 ErrorCode::AlreadyExists,
174 ErrorCode::InvalidArgument,
175 ErrorCode::PermissionDenied,
176 ErrorCode::FailedPrecondition,
177 ErrorCode::ResourceExhausted,
178 ErrorCode::Unavailable,
179 ErrorCode::InternalError,
180 ErrorCode::NotSupported,
181 ErrorCode::Unknown,
182 ];
183 for code in codes {
184 let numeric: u32 = code.into();
185 assert!(
186 (1..=10).contains(&numeric),
187 "code {:?} mapped to {}",
188 code,
189 numeric
190 );
191 }
192 }
193
194 #[test]
195 fn error_code_display() {
196 assert_eq!(ErrorCode::NotFound.to_string(), "NotFound");
197 assert_eq!(ErrorCode::AlreadyExists.to_string(), "AlreadyExists");
198 assert_eq!(ErrorCode::InvalidArgument.to_string(), "InvalidArgument");
199 assert_eq!(ErrorCode::PermissionDenied.to_string(), "PermissionDenied");
200 assert_eq!(
201 ErrorCode::FailedPrecondition.to_string(),
202 "FailedPrecondition"
203 );
204 assert_eq!(
205 ErrorCode::ResourceExhausted.to_string(),
206 "ResourceExhausted"
207 );
208 assert_eq!(ErrorCode::Unavailable.to_string(), "Unavailable");
209 assert_eq!(ErrorCode::InternalError.to_string(), "InternalError");
210 assert_eq!(ErrorCode::NotSupported.to_string(), "NotSupported");
211 assert_eq!(ErrorCode::Unknown.to_string(), "Unknown");
212 }
213
214 #[test]
215 fn error_is_helpers() {
216 let not_found = Error {
217 code: ErrorCode::NotFound,
218 message: "item missing".into(),
219 };
220 assert!(not_found.is_not_found());
221 assert!(!not_found.is_already_exists());
222 assert!(!not_found.is_invalid_argument());
223
224 let already_exists = Error {
225 code: ErrorCode::AlreadyExists,
226 message: "duplicate".into(),
227 };
228 assert!(!already_exists.is_not_found());
229 assert!(already_exists.is_already_exists());
230 assert!(!already_exists.is_invalid_argument());
231
232 let invalid_arg = Error {
233 code: ErrorCode::InvalidArgument,
234 message: "bad param".into(),
235 };
236 assert!(!invalid_arg.is_not_found());
237 assert!(!invalid_arg.is_already_exists());
238 assert!(invalid_arg.is_invalid_argument());
239 }
240
241 #[test]
242 fn error_display_format() {
243 let err = Error {
244 code: ErrorCode::InternalError,
245 message: "something broke".into(),
246 };
247 assert_eq!(err.to_string(), "zvec error InternalError: something broke");
248 }
249
250 #[test]
251 fn error_implements_std_error() {
252 let err = Error {
253 code: ErrorCode::NotFound,
254 message: "gone".into(),
255 };
256 let std_err: &dyn std::error::Error = &err;
257 assert!(std_err.to_string().contains("NotFound"));
258 }
259
260 #[test]
261 fn error_clone() {
262 let err = Error {
263 code: ErrorCode::Unavailable,
264 message: "service down".into(),
265 };
266 let cloned = err.clone();
267 assert_eq!(cloned.code, ErrorCode::Unavailable);
268 assert_eq!(cloned.message, "service down");
269 }
270
271 #[test]
272 fn to_cstring_valid_string() {
273 let result = to_cstring("hello");
274 assert!(result.is_ok());
275 assert_eq!(result.unwrap().to_str().unwrap(), "hello");
276 }
277
278 #[test]
279 fn to_cstring_empty_string() {
280 let result = to_cstring("");
281 assert!(result.is_ok());
282 assert_eq!(result.unwrap().to_str().unwrap(), "");
283 }
284
285 #[test]
286 fn to_cstring_with_null_byte_returns_error() {
287 let result = to_cstring("hello\0world");
288 assert!(result.is_err());
289 let err = result.unwrap_err();
290 assert!(err.is_invalid_argument());
291 assert!(err.message.contains("null byte"));
292 }
293
294 #[test]
295 fn to_cstring_unicode() {
296 let result = to_cstring("你好世界🌍");
297 assert!(result.is_ok());
298 assert_eq!(result.unwrap().to_str().unwrap(), "你好世界🌍");
299 }
300
301 #[test]
302 fn error_code_copy_semantics() {
303 let code = ErrorCode::NotFound;
304 let copied = code;
305 assert_eq!(code, copied);
306 }
307
308 #[test]
309 fn error_code_eq() {
310 assert_eq!(ErrorCode::NotFound, ErrorCode::NotFound);
311 assert_ne!(ErrorCode::NotFound, ErrorCode::AlreadyExists);
312 }
313}