use std::ffi::CString;
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExtensionError {
message: String,
}
impl ExtensionError {
#[inline]
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
}
}
#[inline]
pub fn from_error<E: std::error::Error>(e: E) -> Self {
Self {
message: e.to_string(),
}
}
#[must_use]
pub fn to_c_string(&self) -> CString {
CString::new(self.message.as_bytes()).unwrap_or_else(|_| {
let pos = self
.message
.bytes()
.position(|b| b == 0)
.unwrap_or(self.message.len());
CString::new(&self.message.as_bytes()[..pos]).unwrap_or_else(|_| {
CString::new("extension error (message contained null bytes)")
.unwrap_or_else(|_| CString::default())
})
})
}
#[must_use]
#[inline]
pub fn as_str(&self) -> &str {
&self.message
}
}
impl fmt::Display for ExtensionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.message)
}
}
impl std::error::Error for ExtensionError {}
impl From<&str> for ExtensionError {
#[inline]
fn from(s: &str) -> Self {
Self::new(s)
}
}
impl From<String> for ExtensionError {
#[inline]
fn from(s: String) -> Self {
Self { message: s }
}
}
impl From<Box<dyn std::error::Error>> for ExtensionError {
#[inline]
fn from(e: Box<dyn std::error::Error>) -> Self {
Self {
message: e.to_string(),
}
}
}
impl From<Box<dyn std::error::Error + Send + Sync>> for ExtensionError {
#[inline]
fn from(e: Box<dyn std::error::Error + Send + Sync>) -> Self {
Self {
message: e.to_string(),
}
}
}
impl From<std::io::Error> for ExtensionError {
#[inline]
fn from(e: std::io::Error) -> Self {
Self {
message: e.to_string(),
}
}
}
impl From<std::ffi::NulError> for ExtensionError {
#[inline]
fn from(e: std::ffi::NulError) -> Self {
Self {
message: e.to_string(),
}
}
}
impl From<std::fmt::Error> for ExtensionError {
#[inline]
fn from(e: std::fmt::Error) -> Self {
Self {
message: e.to_string(),
}
}
}
pub type ExtResult<T> = Result<T, ExtensionError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_creates_with_message() {
let err = ExtensionError::new("test error");
assert_eq!(err.to_string(), "test error");
assert_eq!(err.as_str(), "test error");
}
#[test]
fn from_str() {
let err = ExtensionError::from("from str");
assert_eq!(err.message, "from str");
}
#[test]
fn from_string() {
let s = String::from("from String");
let err = ExtensionError::from(s);
assert_eq!(err.message, "from String");
}
#[test]
fn from_error_wraps_display() {
let parse_err = "abc".parse::<i32>().unwrap_err();
let err = ExtensionError::from_error(parse_err);
assert!(!err.message.is_empty());
}
#[test]
fn to_c_string_normal() {
let err = ExtensionError::new("hello world");
let cstr = err.to_c_string();
assert_eq!(cstr.to_str().unwrap(), "hello world");
}
#[test]
fn to_c_string_with_null_byte() {
let err = ExtensionError::new("before\0after");
let cstr = err.to_c_string();
assert_eq!(cstr.to_str().unwrap(), "before");
}
#[test]
fn to_c_string_empty() {
let err = ExtensionError::new("");
let cstr = err.to_c_string();
assert_eq!(cstr.to_str().unwrap(), "");
}
#[test]
fn display_impl() {
let err = ExtensionError::new("display test");
let s = format!("{err}");
assert_eq!(s, "display test");
}
#[test]
fn debug_impl() {
let err = ExtensionError::new("debug");
let s = format!("{err:?}");
assert!(s.contains("debug"));
}
#[test]
fn clone_eq() {
let err1 = ExtensionError::new("clone test");
let err2 = err1.clone();
assert_eq!(err1, err2);
}
#[test]
fn from_box_dyn_error() {
let boxed: Box<dyn std::error::Error> = "abc".parse::<i32>().unwrap_err().into();
let err = ExtensionError::from(boxed);
assert!(!err.message.is_empty());
}
#[test]
fn question_mark_operator_with_str() {
fn fails() -> Result<(), ExtensionError> {
Err("explicit error")?;
Ok(())
}
assert_eq!(fails().unwrap_err().as_str(), "explicit error");
}
#[test]
fn from_io_error() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
let err = ExtensionError::from(io_err);
assert_eq!(err.as_str(), "file not found");
}
#[test]
fn question_mark_with_io_error() {
fn fails() -> Result<(), ExtensionError> {
Err(std::io::Error::other("runtime init failed"))?;
Ok(())
}
assert_eq!(fails().unwrap_err().as_str(), "runtime init failed");
}
#[test]
fn from_nul_error() {
let nul_err = std::ffi::CString::new("hello\0world").unwrap_err();
let err = ExtensionError::from(nul_err);
assert!(!err.as_str().is_empty());
}
#[test]
fn from_fmt_error() {
let fmt_err = std::fmt::Error;
let err = ExtensionError::from(fmt_err);
assert!(!err.as_str().is_empty());
}
#[test]
fn to_c_string_leading_null_byte() {
let err = ExtensionError::new("\0trailing");
let cstr = err.to_c_string();
assert_eq!(cstr.to_str().unwrap(), "");
}
#[test]
fn to_c_string_multiple_null_bytes() {
let err = ExtensionError::new("first\0second\0third");
let cstr = err.to_c_string();
assert_eq!(cstr.to_str().unwrap(), "first");
}
#[test]
fn from_box_dyn_error_send_sync() {
let boxed: Box<dyn std::error::Error + Send + Sync> =
"abc".parse::<i32>().unwrap_err().into();
let err = ExtensionError::from(boxed);
assert!(!err.message.is_empty());
}
#[test]
fn ext_result_alias() {
let ok_val: ExtResult<i32> = Ok(42);
assert!(ok_val.is_ok());
let err_val: ExtResult<i32> = Err(ExtensionError::new("fail"));
assert!(err_val.is_err());
}
}