whatwg_streams 0.1.0-alpha.11

whatwg_streams for rust
Documentation
use crate::platform::{MaybeSend, MaybeSync, SharedPtr};
use std::{error::Error, fmt};

#[derive(Debug, Clone)]
pub enum StreamError {
    Canceled,
    Aborted(Option<String>),
    Closing,
    Closed,
    TaskDropped,
    #[cfg(feature = "send")]
    Other(SharedPtr<dyn Error + Send + Sync>),
    #[cfg(feature = "local")]
    Other(SharedPtr<dyn Error>),
}

impl StreamError {
    /// Wrap any error type into `StreamError`
    pub fn other<E>(e: E) -> Self
    where
        E: Error + MaybeSend + MaybeSync + 'static,
    {
        StreamError::Other(SharedPtr::new(e))
    }

    /// Wrap a boxed error
    #[cfg(feature = "send")]
    pub fn other_boxed(e: Box<dyn Error + Send + Sync>) -> Self {
        StreamError::Other(e.into())
    }

    /// Wrap a boxed error
    #[cfg(feature = "local")]
    pub fn other_boxed(e: Box<dyn Error>) -> Self {
        StreamError::Other(e.into())
    }
}

impl From<&str> for StreamError {
    fn from(s: &str) -> Self {
        #[derive(Debug)]
        struct SimpleError(String);
        impl fmt::Display for SimpleError {
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                write!(f, "{}", self.0)
            }
        }
        impl Error for SimpleError {}
        StreamError::Other(SharedPtr::new(SimpleError(s.to_string())))
    }
}

impl From<String> for StreamError {
    fn from(s: String) -> Self {
        StreamError::from(s.as_str())
    }
}

impl From<std::io::Error> for StreamError {
    fn from(e: std::io::Error) -> Self {
        StreamError::Other(SharedPtr::new(e))
    }
}

#[cfg(feature = "send")]
impl From<Box<dyn Error + Send + Sync>> for StreamError {
    fn from(e: Box<dyn Error + Send + Sync>) -> Self {
        StreamError::Other(e.into())
    }
}

#[cfg(feature = "local")]
impl From<Box<dyn Error>> for StreamError {
    fn from(e: Box<dyn Error>) -> Self {
        StreamError::Other(e.into())
    }
}

#[macro_export]
macro_rules! impl_stream_error_from {
    ($($error_type:ty),* $(,)?) => {
        $(
            impl From<$error_type> for $crate::streams::error::StreamError {
                fn from(e: $error_type) -> Self {
                    $crate::streams::error::StreamError::Other($crate::platform::SharedPtr::new(e))
                }
            }
        )*
    };
}

impl fmt::Display for StreamError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            StreamError::Canceled => write!(f, "Stream operation was canceled"),
            StreamError::Aborted(Some(reason)) => write!(f, "Stream was aborted: {}", reason),
            StreamError::Aborted(None) => write!(f, "Stream was aborted"),
            StreamError::Closing => write!(f, "Stream is closing"),
            StreamError::Closed => write!(f, "Stream is closed"),
            StreamError::TaskDropped => write!(f, "Stream task was dropped"),
            StreamError::Other(err) => write!(f, "{}", err),
        }
    }
}

impl PartialEq for StreamError {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (Self::Canceled, Self::Canceled) => true,
            (Self::Closing, Self::Closing) => true,
            (Self::Closed, Self::Closed) => true,
            (Self::TaskDropped, Self::TaskDropped) => true,
            (Self::Aborted(a), Self::Aborted(b)) => a == b,
            // Two `Other` errors are equal only if they wrap the same allocation.
            // Comparing by Display would be misleading (different errors can format identically).
            #[cfg(feature = "send")]
            (Self::Other(a), Self::Other(b)) => std::sync::Arc::ptr_eq(a, b),
            #[cfg(feature = "local")]
            (Self::Other(a), Self::Other(b)) => std::rc::Rc::ptr_eq(a, b),
            _ => false,
        }
    }
}

impl Eq for StreamError {}

impl Error for StreamError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            StreamError::Other(err) => Some(err.as_ref()),
            _ => None,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_all_conversions_work() {
        let _: StreamError = "error message".into();
        let _: StreamError = String::from("error").into();

        let io_err = std::io::Error::new(std::io::ErrorKind::Other, "io error");
        let _: StreamError = io_err.into();

        #[derive(Debug)]
        struct CustomError;
        impl fmt::Display for CustomError {
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                write!(f, "custom error")
            }
        }
        impl Error for CustomError {}

        let _: StreamError = StreamError::other(CustomError);
    }

    #[test]
    fn test_question_mark_propagates() {
        fn returns_stream_error() -> Result<(), StreamError> {
            Err("stream error".into())
        }

        // Verify that `?` propagates StreamError through Box<dyn Error>
        fn propagate() -> Result<(), Box<dyn Error>> {
            returns_stream_error()?;
            Ok(())
        }

        let result = propagate();
        assert!(result.is_err(), "StreamError should propagate via ?");
    }

    #[test]
    fn test_macro_usage_example() -> Result<(), Box<dyn Error>> {
        #[derive(Debug)]
        struct UserCustomError(String);
        impl fmt::Display for UserCustomError {
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                write!(f, "User error: {}", self.0)
            }
        }
        impl Error for UserCustomError {}

        impl_stream_error_from!(UserCustomError);

        fn user_function() -> Result<(), StreamError> {
            fn might_fail() -> Result<(), UserCustomError> {
                Err(UserCustomError("something went wrong".to_string()))
            }
            might_fail()?; // works via macro
            Ok(())
        }

        assert!(user_function().is_err());
        Ok(())
    }

    #[test]
    fn test_mixed_error_handling() {
        #[cfg(feature = "local")]
        fn might_fail_json() -> Result<(), Box<dyn Error>> {
            Err("json parse error".into())
        }

        #[cfg(feature = "send")]
        fn might_fail_json() -> Result<(), Box<dyn Error + Send + Sync>> {
            Err("json parse error".into())
        }

        #[derive(Debug)]
        struct CustomError(String);
        impl fmt::Display for CustomError {
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                write!(f, "{}", self.0)
            }
        }
        impl Error for CustomError {}

        fn might_fail_custom() -> Result<(), CustomError> {
            Err(CustomError("custom failure".to_string()))
        }

        // Verify all conversion paths produce StreamError
        fn propagate_all() -> Result<(), StreamError> {
            might_fail_json().map_err(StreamError::other_boxed)?;
            might_fail_custom().map_err(StreamError::other)?;
            might_fail_json()?; // via From<Box<dyn Error>>
            Ok(())
        }

        // The first map_err should convert and propagate
        let result = propagate_all();
        assert!(result.is_err(), "mixed error handling should propagate");
    }
}