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 {
pub fn other<E>(e: E) -> Self
where
E: Error + MaybeSend + MaybeSync + 'static,
{
StreamError::Other(SharedPtr::new(e))
}
#[cfg(feature = "send")]
pub fn other_boxed(e: Box<dyn Error + Send + Sync>) -> Self {
StreamError::Other(e.into())
}
#[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,
#[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())
}
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()?; 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()))
}
fn propagate_all() -> Result<(), StreamError> {
might_fail_json().map_err(StreamError::other_boxed)?;
might_fail_custom().map_err(StreamError::other)?;
might_fail_json()?; Ok(())
}
let result = propagate_all();
assert!(result.is_err(), "mixed error handling should propagate");
}
}