use std::borrow::Borrow;
use std::borrow::Cow;
use std::error;
use std::error::Error as _;
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::result;
pub type Result<T, E = Error> = result::Result<T, E>;
#[allow(clippy::wildcard_imports)]
mod private {
use super::*;
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 {}
}
#[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 {
Io(io::Error),
ContextOwned {
context: Box<str>,
source: Box<Self>,
},
ContextStatic {
context: &'static str,
source: Box<Self>,
},
}
impl ErrorImpl {
fn kind(&self) -> ErrorKind {
match self {
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::Interrupted => ErrorKind::Interrupted,
io::ErrorKind::Unsupported => ErrorKind::Unsupported,
io::ErrorKind::UnexpectedEof => ErrorKind::UnexpectedEof,
io::ErrorKind::OutOfMemory => ErrorKind::OutOfMemory,
_ => ErrorKind::Other,
},
Self::ContextOwned { source, .. } | Self::ContextStatic { source, .. } => {
source.deref().kind()
}
}
}
#[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 {
Self::Io(io) => {
dbg = f.debug_tuple(stringify!(Io));
dbg.field(io)
}
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 {
Self::Io(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();
}
}
Ok(())
}
}
}
impl Display for ErrorImpl {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let () = match self {
Self::Io(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 error::Error for ErrorImpl {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
Self::Io(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,
TimedOut,
WriteZero,
Interrupted,
Unsupported,
UnexpectedEof,
OutOfMemory,
Other,
}
#[repr(transparent)]
pub struct Error {
error: Box<ErrorImpl>,
}
impl Error {
#[inline]
pub fn from_raw_os_error(code: i32) -> Self {
debug_assert!(
code > 0,
"OS error code should be positive integer; got: {code}"
);
Self::from(io::Error::from_raw_os_error(code))
}
#[inline]
pub(crate) 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_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 fn kind(&self) -> ErrorKind {
self.error.kind()
}
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 error::Error for Error {
#[inline]
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
self.error.source()
}
}
impl From<io::Error> for Error {
fn from(other: io::Error) -> Self {
Self {
error: Box::new(ErrorImpl::Io(other)),
}
}
}
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 = Self;
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)
}
}
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().to_string()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::mem::size_of;
#[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>());
assert_eq!(size_of::<ErrorImpl>(), 4 * size_of::<usize>());
}
#[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_eq!(format!("{err:?}"), "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:#?}"), "");
}
}