1use std::ffi::CString;
22use std::fmt;
23
24#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct ExtensionError {
47 message: String,
48}
49
50impl ExtensionError {
51 #[inline]
62 pub fn new(message: impl Into<String>) -> Self {
63 Self {
64 message: message.into(),
65 }
66 }
67
68 #[inline]
80 pub fn from_error<E: std::error::Error>(e: E) -> Self {
81 Self {
82 message: e.to_string(),
83 }
84 }
85
86 #[must_use]
101 pub fn to_c_string(&self) -> CString {
102 CString::new(self.message.as_bytes()).unwrap_or_else(|_| {
103 let pos = self
107 .message
108 .bytes()
109 .position(|b| b == 0)
110 .unwrap_or(self.message.len());
111 CString::new(&self.message.as_bytes()[..pos]).unwrap_or_else(|_| {
112 CString::new("extension error (message contained null bytes)")
114 .unwrap_or_else(|_| CString::default())
115 })
116 })
117 }
118
119 #[must_use]
130 #[inline]
131 pub fn as_str(&self) -> &str {
132 &self.message
133 }
134}
135
136impl fmt::Display for ExtensionError {
137 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138 f.write_str(&self.message)
139 }
140}
141
142impl std::error::Error for ExtensionError {}
143
144impl From<&str> for ExtensionError {
145 #[inline]
146 fn from(s: &str) -> Self {
147 Self::new(s)
148 }
149}
150
151impl From<String> for ExtensionError {
152 #[inline]
153 fn from(s: String) -> Self {
154 Self { message: s }
155 }
156}
157
158impl From<Box<dyn std::error::Error>> for ExtensionError {
159 #[inline]
160 fn from(e: Box<dyn std::error::Error>) -> Self {
161 Self {
162 message: e.to_string(),
163 }
164 }
165}
166
167impl From<Box<dyn std::error::Error + Send + Sync>> for ExtensionError {
168 #[inline]
169 fn from(e: Box<dyn std::error::Error + Send + Sync>) -> Self {
170 Self {
171 message: e.to_string(),
172 }
173 }
174}
175
176impl From<std::io::Error> for ExtensionError {
177 #[inline]
178 fn from(e: std::io::Error) -> Self {
179 Self {
180 message: e.to_string(),
181 }
182 }
183}
184
185impl From<std::ffi::NulError> for ExtensionError {
186 #[inline]
187 fn from(e: std::ffi::NulError) -> Self {
188 Self {
189 message: e.to_string(),
190 }
191 }
192}
193
194impl From<std::fmt::Error> for ExtensionError {
195 #[inline]
196 fn from(e: std::fmt::Error) -> Self {
197 Self {
198 message: e.to_string(),
199 }
200 }
201}
202
203pub type ExtResult<T> = Result<T, ExtensionError>;
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 #[test]
211 fn new_creates_with_message() {
212 let err = ExtensionError::new("test error");
213 assert_eq!(err.to_string(), "test error");
214 assert_eq!(err.as_str(), "test error");
215 }
216
217 #[test]
218 fn from_str() {
219 let err = ExtensionError::from("from str");
220 assert_eq!(err.message, "from str");
221 }
222
223 #[test]
224 fn from_string() {
225 let s = String::from("from String");
226 let err = ExtensionError::from(s);
227 assert_eq!(err.message, "from String");
228 }
229
230 #[test]
231 fn from_error_wraps_display() {
232 let parse_err = "abc".parse::<i32>().unwrap_err();
233 let err = ExtensionError::from_error(parse_err);
234 assert!(!err.message.is_empty());
235 }
236
237 #[test]
238 fn to_c_string_normal() {
239 let err = ExtensionError::new("hello world");
240 let cstr = err.to_c_string();
241 assert_eq!(cstr.to_str().unwrap(), "hello world");
242 }
243
244 #[test]
245 fn to_c_string_with_null_byte() {
246 let err = ExtensionError::new("before\0after");
248 let cstr = err.to_c_string();
249 assert_eq!(cstr.to_str().unwrap(), "before");
250 }
251
252 #[test]
253 fn to_c_string_empty() {
254 let err = ExtensionError::new("");
255 let cstr = err.to_c_string();
256 assert_eq!(cstr.to_str().unwrap(), "");
257 }
258
259 #[test]
260 fn display_impl() {
261 let err = ExtensionError::new("display test");
262 let s = format!("{err}");
263 assert_eq!(s, "display test");
264 }
265
266 #[test]
267 fn debug_impl() {
268 let err = ExtensionError::new("debug");
269 let s = format!("{err:?}");
270 assert!(s.contains("debug"));
271 }
272
273 #[test]
274 fn clone_eq() {
275 let err1 = ExtensionError::new("clone test");
276 let err2 = err1.clone();
277 assert_eq!(err1, err2);
278 }
279
280 #[test]
281 fn from_box_dyn_error() {
282 let boxed: Box<dyn std::error::Error> = "abc".parse::<i32>().unwrap_err().into();
283 let err = ExtensionError::from(boxed);
284 assert!(!err.message.is_empty());
285 }
286
287 #[test]
288 fn question_mark_operator_with_str() {
289 fn fails() -> Result<(), ExtensionError> {
290 Err("explicit error")?;
291 Ok(())
292 }
293 assert_eq!(fails().unwrap_err().as_str(), "explicit error");
294 }
295
296 #[test]
297 fn from_io_error() {
298 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
299 let err = ExtensionError::from(io_err);
300 assert_eq!(err.as_str(), "file not found");
301 }
302
303 #[test]
304 fn question_mark_with_io_error() {
305 fn fails() -> Result<(), ExtensionError> {
306 Err(std::io::Error::other("runtime init failed"))?;
307 Ok(())
308 }
309 assert_eq!(fails().unwrap_err().as_str(), "runtime init failed");
310 }
311
312 #[test]
313 fn from_nul_error() {
314 let nul_err = std::ffi::CString::new("hello\0world").unwrap_err();
315 let err = ExtensionError::from(nul_err);
316 assert!(!err.as_str().is_empty());
317 }
318
319 #[test]
320 fn from_fmt_error() {
321 let fmt_err = std::fmt::Error;
322 let err = ExtensionError::from(fmt_err);
323 assert!(!err.as_str().is_empty());
324 }
325
326 #[test]
327 fn to_c_string_leading_null_byte() {
328 let err = ExtensionError::new("\0trailing");
330 let cstr = err.to_c_string();
331 assert_eq!(cstr.to_str().unwrap(), "");
332 }
333
334 #[test]
335 fn to_c_string_multiple_null_bytes() {
336 let err = ExtensionError::new("first\0second\0third");
337 let cstr = err.to_c_string();
338 assert_eq!(cstr.to_str().unwrap(), "first");
339 }
340
341 #[test]
342 fn from_box_dyn_error_send_sync() {
343 let boxed: Box<dyn std::error::Error + Send + Sync> =
344 "abc".parse::<i32>().unwrap_err().into();
345 let err = ExtensionError::from(boxed);
346 assert!(!err.message.is_empty());
347 }
348
349 #[test]
350 fn ext_result_alias() {
351 let ok_val: ExtResult<i32> = Ok(42);
353 assert!(ok_val.is_ok());
354
355 let err_val: ExtResult<i32> = Err(ExtensionError::new("fail"));
356 assert!(err_val.is_err());
357 }
358}