Skip to main content

zvec_rust/
error.rs

1use std::ffi::{CStr, CString};
2use std::fmt;
3use std::os::raw::c_void;
4
5/// Error codes returned by zvec operations.
6#[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/// An error returned by zvec operations.
72#[derive(Debug, Clone, PartialEq, Eq)]
73pub struct Error {
74    pub code: ErrorCode,
75    pub message: String,
76}
77
78impl Error {
79    /// Returns `true` if this is a "not found" error.
80    pub fn is_not_found(&self) -> bool {
81        self.code == ErrorCode::NotFound
82    }
83
84    /// Returns `true` if this is an "already exists" error.
85    pub fn is_already_exists(&self) -> bool {
86        self.code == ErrorCode::AlreadyExists
87    }
88
89    /// Returns `true` if this is an "invalid argument" error.
90    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
103/// A specialized `Result` type for zvec operations.
104pub type Result<T> = std::result::Result<T, Error>;
105
106/// Convert a C error code to a Rust `Result`.
107///
108/// Returns `Ok(())` if the code is `ZVEC_OK`, otherwise fetches the last error
109/// message from the C library and returns an appropriate `Error`.
110pub(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
135/// Create a `CString` from a `&str`, returning an `InvalidArgument` error
136/// if the string contains a null byte.
137pub(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}