use std::backtrace::Backtrace;
use std::backtrace::BacktraceStatus;
use std::borrow::Borrow;
use std::borrow::Cow;
use std::error::Error as StdError;
use std::fmt::Debug;
use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Result as FmtResult;
use std::io;
use std::mem::transmute;
use std::ops::Deref;
use std::str;
mod private {
use super::io;
use super::str;
use super::Error;
pub trait Sealed {}
impl<T> Sealed for Option<T> {}
impl<T, E> Sealed for Result<T, E> {}
impl Sealed for &'static str {}
impl Sealed for String {}
impl Sealed for Error {}
impl Sealed for io::Error {}
#[cfg(feature = "breakpad")]
#[allow(clippy::absolute_paths)]
impl Sealed for (&[u8], nom::Err<nom::error::VerboseError<&[u8]>>) {}
#[cfg(feature = "dwarf")]
impl Sealed for gimli::Error {}
}
#[derive(Debug)]
#[repr(transparent)]
#[doc(hidden)]
pub struct Str(str);
impl ToOwned for Str {
type Owned = Box<str>;
#[inline]
fn to_owned(&self) -> Self::Owned {
self.0.to_string().into_boxed_str()
}
}
impl Borrow<Str> for Box<str> {
#[inline]
fn borrow(&self) -> &Str {
unsafe { transmute::<&str, &Str>(self.deref()) }
}
}
impl Deref for Str {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Display for Str {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
Display::fmt(&self.0, f)
}
}
pub trait IntoCowStr: private::Sealed {
fn into_cow_str(self) -> Cow<'static, Str>;
}
impl IntoCowStr for &'static str {
fn into_cow_str(self) -> Cow<'static, Str> {
let other = unsafe { transmute::<&str, &Str>(self) };
Cow::Borrowed(other)
}
}
impl IntoCowStr for String {
fn into_cow_str(self) -> Cow<'static, Str> {
Cow::Owned(self.into_boxed_str())
}
}
enum ErrorImpl {
#[cfg(feature = "dwarf")]
Dwarf {
error: gimli::Error,
#[cfg(feature = "backtrace")]
backtrace: Backtrace,
},
Io {
error: io::Error,
#[cfg(feature = "backtrace")]
backtrace: Backtrace,
},
Std {
error: Box<dyn StdError + Send + Sync + 'static>,
#[cfg(feature = "backtrace")]
backtrace: Backtrace,
},
ContextOwned {
context: Box<str>,
source: Box<ErrorImpl>,
},
ContextStatic {
context: &'static str,
source: Box<ErrorImpl>,
},
}
impl ErrorImpl {
fn kind(&self) -> ErrorKind {
match self {
#[cfg(feature = "dwarf")]
Self::Dwarf { .. } => ErrorKind::InvalidDwarf,
Self::Io { error, .. } => match error.kind() {
io::ErrorKind::NotFound => ErrorKind::NotFound,
io::ErrorKind::PermissionDenied => ErrorKind::PermissionDenied,
io::ErrorKind::AlreadyExists => ErrorKind::AlreadyExists,
io::ErrorKind::WouldBlock => ErrorKind::WouldBlock,
io::ErrorKind::InvalidInput => ErrorKind::InvalidInput,
io::ErrorKind::InvalidData => ErrorKind::InvalidData,
io::ErrorKind::TimedOut => ErrorKind::TimedOut,
io::ErrorKind::WriteZero => ErrorKind::WriteZero,
io::ErrorKind::Unsupported => ErrorKind::Unsupported,
io::ErrorKind::UnexpectedEof => ErrorKind::UnexpectedEof,
io::ErrorKind::OutOfMemory => ErrorKind::OutOfMemory,
_ => ErrorKind::Other,
},
Self::Std { .. } => ErrorKind::Other,
Self::ContextOwned { source, .. } | Self::ContextStatic { source, .. } => {
source.deref().kind()
}
}
}
#[cfg(feature = "backtrace")]
fn backtrace(&self) -> Option<&Backtrace> {
match self {
#[cfg(feature = "dwarf")]
Self::Dwarf { backtrace, .. } => Some(backtrace),
Self::Io { backtrace, .. } => Some(backtrace),
Self::Std { backtrace, .. } => Some(backtrace),
Self::ContextOwned { source, .. } => source.backtrace(),
Self::ContextStatic { source, .. } => source.backtrace(),
}
}
#[cfg(not(feature = "backtrace"))]
fn backtrace(&self) -> Option<&Backtrace> {
None
}
#[cfg(test)]
fn is_owned(&self) -> Option<bool> {
match self {
Self::ContextOwned { .. } => Some(true),
Self::ContextStatic { .. } => Some(false),
_ => None,
}
}
}
impl Debug for ErrorImpl {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
if f.alternate() {
let mut dbg;
match self {
#[cfg(feature = "dwarf")]
Self::Dwarf { error, .. } => {
dbg = f.debug_tuple(stringify!(Dwarf));
dbg.field(error)
}
Self::Io { error, .. } => {
dbg = f.debug_tuple(stringify!(Io));
dbg.field(error)
}
Self::Std { error, .. } => {
dbg = f.debug_tuple(stringify!(Std));
dbg.field(error)
}
Self::ContextOwned { context, .. } => {
dbg = f.debug_tuple(stringify!(ContextOwned));
dbg.field(context)
}
Self::ContextStatic { context, .. } => {
dbg = f.debug_tuple(stringify!(ContextStatic));
dbg.field(context)
}
}
.finish()
} else {
let () = match self {
#[cfg(feature = "dwarf")]
Self::Dwarf { error, .. } => write!(f, "Error: {error}")?,
Self::Io { error, .. } => write!(f, "Error: {error}")?,
Self::Std { error, .. } => write!(f, "Error: {error}")?,
Self::ContextOwned { context, .. } => write!(f, "Error: {context}")?,
Self::ContextStatic { context, .. } => write!(f, "Error: {context}")?,
};
if let Some(source) = self.source() {
let () = f.write_str("\n\nCaused by:")?;
let mut error = Some(source);
while let Some(err) = error {
let () = write!(f, "\n {err:}")?;
error = err.source();
}
}
match self.backtrace() {
Some(backtrace) if backtrace.status() == BacktraceStatus::Captured => {
let () = write!(f, "\n\nStack backtrace:\n{backtrace}")?;
}
_ => (),
}
Ok(())
}
}
}
impl Display for ErrorImpl {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let () = match self {
#[cfg(feature = "dwarf")]
Self::Dwarf { error, .. } => Display::fmt(error, f)?,
Self::Io { error, .. } => Display::fmt(error, f)?,
Self::Std { error, .. } => Display::fmt(error, f)?,
Self::ContextOwned { context, .. } => Display::fmt(context, f)?,
Self::ContextStatic { context, .. } => Display::fmt(context, f)?,
};
if f.alternate() {
let mut error = self.source();
while let Some(err) = error {
let () = write!(f, ": {err}")?;
error = err.source();
}
}
Ok(())
}
}
impl StdError for ErrorImpl {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self {
#[cfg(feature = "dwarf")]
Self::Dwarf { error, .. } => error.source(),
Self::Io { error, .. } => error.source(),
Self::Std { error, .. } => error.source(),
Self::ContextOwned { source, .. } | Self::ContextStatic { source, .. } => Some(source),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
pub enum ErrorKind {
NotFound,
PermissionDenied,
AlreadyExists,
WouldBlock,
InvalidInput,
InvalidData,
InvalidDwarf,
TimedOut,
WriteZero,
Unsupported,
UnexpectedEof,
OutOfMemory,
Other,
}
impl ErrorKind {
#[doc(hidden)]
#[inline]
pub fn as_bytes(&self) -> &'static [u8] {
match self {
Self::AlreadyExists => b"entity already exists\0",
Self::InvalidData => b"invalid data\0",
Self::InvalidInput => b"invalid input parameter\0",
Self::NotFound => b"entity not found\0",
Self::Other => b"other error\0",
Self::OutOfMemory => b"out of memory\0",
Self::PermissionDenied => b"permission denied\0",
Self::TimedOut => b"timed out\0",
Self::UnexpectedEof => b"unexpected end of file\0",
Self::Unsupported => b"unsupported\0",
Self::WouldBlock => b"operation would block\0",
Self::WriteZero => b"write zero\0",
Self::InvalidDwarf => b"DWARF data invalid\0",
}
}
}
impl Display for ErrorKind {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let cstr = self.as_bytes();
let s = unsafe { str::from_utf8_unchecked(&cstr[..cstr.len() - 1]) };
f.write_str(s)
}
}
#[repr(transparent)]
pub struct Error {
error: Box<ErrorImpl>,
}
impl Error {
#[cold]
fn with_io_error<E>(kind: io::ErrorKind, error: E) -> Self
where
E: ToString,
{
Self::from(io::Error::new(kind, error.to_string()))
}
#[inline]
pub(crate) fn with_not_found<E>(error: E) -> Self
where
E: ToString,
{
Self::with_io_error(io::ErrorKind::NotFound, error)
}
#[inline]
pub(crate) fn with_invalid_data<E>(error: E) -> Self
where
E: ToString,
{
Self::with_io_error(io::ErrorKind::InvalidData, error)
}
#[inline]
pub(crate) fn with_invalid_input<E>(error: E) -> Self
where
E: ToString,
{
Self::with_io_error(io::ErrorKind::InvalidInput, error)
}
#[inline]
pub(crate) fn with_unsupported<E>(error: E) -> Self
where
E: ToString,
{
Self::with_io_error(io::ErrorKind::Unsupported, error)
}
#[inline]
pub fn kind(&self) -> ErrorKind {
self.error.kind()
}
#[cold]
fn layer_context(self, context: Cow<'static, Str>) -> Self {
match context {
Cow::Owned(context) => Self {
error: Box::new(ErrorImpl::ContextOwned {
context,
source: self.error,
}),
},
Cow::Borrowed(context) => Self {
error: Box::new(ErrorImpl::ContextStatic {
context,
source: self.error,
}),
},
}
}
}
impl Debug for Error {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
Debug::fmt(&self.error, f)
}
}
impl Display for Error {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
Display::fmt(&self.error, f)
}
}
impl StdError for Error {
#[inline]
fn source(&self) -> Option<&(dyn StdError + 'static)> {
self.error.source()
}
}
#[cfg(feature = "dwarf")]
impl From<gimli::Error> for Error {
fn from(other: gimli::Error) -> Self {
Self {
error: Box::new(ErrorImpl::Dwarf {
error: other,
#[cfg(feature = "backtrace")]
backtrace: Backtrace::capture(),
}),
}
}
}
impl From<io::Error> for Error {
fn from(other: io::Error) -> Self {
Self {
error: Box::new(ErrorImpl::Io {
error: other,
#[cfg(feature = "backtrace")]
backtrace: Backtrace::capture(),
}),
}
}
}
impl From<Box<dyn StdError + Send + Sync + 'static>> for Error {
fn from(other: Box<dyn StdError + Send + Sync + 'static>) -> Self {
Self {
error: Box::new(ErrorImpl::Std {
error: other,
#[cfg(feature = "backtrace")]
backtrace: Backtrace::capture(),
}),
}
}
}
pub trait ErrorExt: private::Sealed {
type Output;
fn context<C>(self, context: C) -> Self::Output
where
C: IntoCowStr;
fn with_context<C, F>(self, f: F) -> Self::Output
where
C: IntoCowStr,
F: FnOnce() -> C;
}
impl ErrorExt for Error {
type Output = Error;
fn context<C>(self, context: C) -> Self::Output
where
C: IntoCowStr,
{
self.layer_context(context.into_cow_str())
}
fn with_context<C, F>(self, f: F) -> Self::Output
where
C: IntoCowStr,
F: FnOnce() -> C,
{
self.layer_context(f().into_cow_str())
}
}
impl<T, E> ErrorExt for Result<T, E>
where
E: ErrorExt,
{
type Output = Result<T, E::Output>;
fn context<C>(self, context: C) -> Self::Output
where
C: IntoCowStr,
{
match self {
Ok(val) => Ok(val),
Err(err) => Err(err.context(context)),
}
}
fn with_context<C, F>(self, f: F) -> Self::Output
where
C: IntoCowStr,
F: FnOnce() -> C,
{
match self {
Ok(val) => Ok(val),
Err(err) => Err(err.with_context(f)),
}
}
}
impl ErrorExt for io::Error {
type Output = Error;
fn context<C>(self, context: C) -> Self::Output
where
C: IntoCowStr,
{
Error::from(self).context(context)
}
fn with_context<C, F>(self, f: F) -> Self::Output
where
C: IntoCowStr,
F: FnOnce() -> C,
{
Error::from(self).with_context(f)
}
}
pub trait IntoError<T>: private::Sealed
where
Self: Sized,
{
fn ok_or_error<C, F>(self, kind: io::ErrorKind, f: F) -> Result<T, Error>
where
C: ToString,
F: FnOnce() -> C;
#[inline]
fn ok_or_invalid_data<C, F>(self, f: F) -> Result<T, Error>
where
C: ToString,
F: FnOnce() -> C,
{
self.ok_or_error(io::ErrorKind::InvalidData, f)
}
#[inline]
fn ok_or_invalid_input<C, F>(self, f: F) -> Result<T, Error>
where
C: ToString,
F: FnOnce() -> C,
{
self.ok_or_error(io::ErrorKind::InvalidInput, f)
}
#[inline]
fn ok_or_not_found<C, F>(self, f: F) -> Result<T, Error>
where
C: ToString,
F: FnOnce() -> C,
{
self.ok_or_error(io::ErrorKind::NotFound, f)
}
#[inline]
fn ok_or_unexpected_eof<C, F>(self, f: F) -> Result<T, Error>
where
C: ToString,
F: FnOnce() -> C,
{
self.ok_or_error(io::ErrorKind::UnexpectedEof, f)
}
}
impl<T> IntoError<T> for Option<T> {
#[inline]
fn ok_or_error<C, F>(self, kind: io::ErrorKind, f: F) -> Result<T, Error>
where
C: ToString,
F: FnOnce() -> C,
{
self.ok_or_else(|| Error::with_io_error(kind, f()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::env;
use std::mem::size_of;
use test_fork::fork;
use test_log::test;
use test_tag::tag;
#[tag(miri)]
#[test]
fn display_repr() {
assert_eq!(ErrorKind::NotFound.to_string(), "entity not found");
assert_eq!(ErrorKind::OutOfMemory.to_string(), "out of memory");
assert_eq!(
ErrorKind::UnexpectedEof.to_string(),
"unexpected end of file"
);
}
#[tag(miri)]
#[test]
fn str_wrapper() {
let b = "test string".to_string().into_boxed_str();
let s: &Str = b.borrow();
let _b: Box<str> = s.to_owned();
assert_eq!(s.to_string(), b.deref());
assert_eq!(format!("{s:?}"), "Str(\"test string\")");
}
#[test]
fn error_size() {
assert_eq!(size_of::<Error>(), size_of::<usize>());
}
#[tag(miri)]
#[test]
fn std_error_conversion() {
let err = io::Error::new(io::ErrorKind::InvalidData, "some invalid data");
let err = Box::new(err) as Box<dyn StdError + Send + Sync + 'static>;
let err = Error::from(err);
assert_eq!(format!("{err}"), "some invalid data");
assert_eq!(err.kind(), ErrorKind::Other);
}
#[tag(miri)]
#[test]
fn error_kind() {
let err = Error::from(io::Error::new(io::ErrorKind::AlreadyExists, "oops"));
assert_eq!(err.kind(), ErrorKind::AlreadyExists);
let err = Error::from(io::Error::new(io::ErrorKind::WouldBlock, "would block"));
assert_eq!(err.kind(), ErrorKind::WouldBlock);
let err = Error::from(io::Error::new(io::ErrorKind::TimedOut, "Ba Dum Tss"));
assert_eq!(err.kind(), ErrorKind::TimedOut);
let err = Error::from(io::Error::new(io::ErrorKind::WriteZero, "Nothing there :/"));
assert_eq!(err.kind(), ErrorKind::WriteZero);
let err = Error::from(io::Error::new(io::ErrorKind::UnexpectedEof, "Ohh?"));
assert_eq!(err.kind(), ErrorKind::UnexpectedEof);
let err = Error::from(io::Error::new(io::ErrorKind::OutOfMemory, "oom"));
assert_eq!(err.kind(), ErrorKind::OutOfMemory);
}
#[tag(miri)]
#[test]
fn error_formatting() {
let err = io::Error::new(io::ErrorKind::InvalidData, "some invalid data");
let err = Error::from(err);
let src = err.source();
assert!(src.is_none(), "{src:?}");
assert!(err.error.is_owned().is_none());
assert_eq!(err.kind(), ErrorKind::InvalidData);
assert_eq!(format!("{err}"), "some invalid data");
assert_eq!(format!("{err:#}"), "some invalid data");
assert!(format!("{err:?}").starts_with("Error: some invalid data"));
let expected = r#"Io(
Custom {
kind: InvalidData,
error: "some invalid data",
},
)"#;
assert_eq!(format!("{err:#?}"), expected);
let err = err.context("inner context");
let src = err.source();
assert!(src.is_some(), "{src:?}");
assert!(!err.error.is_owned().unwrap());
assert_eq!(err.kind(), ErrorKind::InvalidData);
assert_eq!(format!("{err}"), "inner context");
assert_eq!(format!("{err:#}"), "inner context: some invalid data");
let expected = r#"Error: inner context
Caused by:
some invalid data"#;
assert_eq!(format!("{err:?}"), expected);
assert_ne!(format!("{err:#?}"), "");
let err = err.context("outer context".to_string());
let src = err.source();
assert!(src.is_some(), "{src:?}");
assert!(err.error.is_owned().unwrap());
assert_eq!(err.kind(), ErrorKind::InvalidData);
assert_eq!(format!("{err}"), "outer context");
assert_eq!(
format!("{err:#}"),
"outer context: inner context: some invalid data"
);
let expected = r#"Error: outer context
Caused by:
inner context
some invalid data"#;
assert_eq!(format!("{err:?}"), expected);
assert_ne!(format!("{err:#?}"), "");
}
#[fork]
#[test]
fn error_backtrace() {
if !cfg!(debug_assertions) {
return
}
let () = unsafe { env::set_var("RUST_LIB_BACKTRACE", "1") };
let err = io::Error::new(io::ErrorKind::InvalidData, "some invalid data");
let err = Error::from(err);
let debug = format!("{err:?}");
let start_idx = debug.find("Stack backtrace").unwrap();
let backtrace = &debug[start_idx..];
assert!(backtrace.contains("src/error.rs"), "{backtrace}");
let err = err.context("foobar");
let debug = format!("{err:?}");
let start_idx = debug.find("Stack backtrace").unwrap();
let backtrace = &debug[start_idx..];
assert!(backtrace.contains("src/error.rs"), "{backtrace}");
}
#[fork]
#[test]
fn error_no_backtrace1() {
let () = unsafe { env::remove_var("RUST_BACKTRACE") };
let () = unsafe { env::remove_var("RUST_LIB_BACKTRACE") };
let err = io::Error::new(io::ErrorKind::InvalidData, "some invalid data");
let err = Error::from(err);
let debug = format!("{err:?}");
assert_eq!(debug.find("Stack backtrace"), None);
}
#[fork]
#[test]
fn error_no_backtrace2() {
let () = unsafe { env::remove_var("RUST_BACKTRACE") };
let () = unsafe { env::set_var("RUST_LIB_BACKTRACE", "0") };
let err = io::Error::new(io::ErrorKind::InvalidData, "some invalid data");
let err = Error::from(err);
let debug = format!("{err:?}");
assert_eq!(debug.find("Stack backtrace"), None);
}
}