Skip to main content

eventsrc_client/
error.rs

1use std::{borrow::Cow, error, fmt};
2
3/// Error categories for event source.
4#[non_exhaustive]
5#[derive(Clone, Copy, Debug, Eq, PartialEq)]
6pub enum ErrorKind {
7    /// Transport error.
8    Transport,
9    /// Invalid request error.
10    InvalidRequest,
11    /// Invalid response (status != 200, content-type != text/event-stream or body stream error).
12    InvalidResponse,
13    /// Event source protocol error.
14    Protocol,
15}
16
17impl fmt::Display for ErrorKind {
18    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19        match self {
20            Self::Transport => write!(f, "Transport")?,
21            Self::InvalidRequest => write!(f, "InvalidRequest")?,
22            Self::InvalidResponse => write!(f, "InvalidResponse")?,
23            Self::Protocol => write!(f, "SseProtocol")?,
24        }
25        Ok(())
26    }
27}
28
29/// The lower-level source of [`Error`].
30///
31/// NOTE: we don't implement `core::error::Error` for `ErrorSource`.
32pub struct ErrorSource(anyhow::Error);
33
34impl fmt::Debug for ErrorSource {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        fmt::Debug::fmt(&self.0, f)
37    }
38}
39
40impl fmt::Display for ErrorSource {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        fmt::Display::fmt(&self.0, f)
43    }
44}
45
46impl From<String> for ErrorSource {
47    fn from(message: String) -> Self {
48        ErrorSource(anyhow::Error::msg(message))
49    }
50}
51
52impl From<&'static str> for ErrorSource {
53    fn from(message: &'static str) -> Self {
54        ErrorSource(anyhow::Error::msg(message))
55    }
56}
57
58/// Conversion trait for attaching source errors.
59pub trait IntoErrorSource {
60    /// Converts the error into an error source.
61    fn into_error_source(self) -> ErrorSource;
62}
63
64impl IntoErrorSource for ErrorSource {
65    fn into_error_source(self) -> ErrorSource {
66        self
67    }
68}
69
70impl<E> IntoErrorSource for E
71where
72    E: Into<anyhow::Error>,
73{
74    fn into_error_source(self) -> ErrorSource {
75        ErrorSource(self.into())
76    }
77}
78
79/// Error type for event source.
80pub struct Error {
81    kind: ErrorKind,
82    message: Cow<'static, str>,
83    context: Vec<(&'static str, String)>,
84    source: Option<ErrorSource>,
85}
86
87impl Error {
88    /// Creates a new error with the given kind and message.
89    pub fn new(kind: ErrorKind, message: impl Into<Cow<'static, str>>) -> Self {
90        Self { kind, message: message.into(), context: Vec::new(), source: None }
91    }
92
93    /// Returns the error kind.
94    pub const fn kind(&self) -> ErrorKind {
95        self.kind
96    }
97
98    /// Returns the error message.
99    pub fn message(&self) -> &str {
100        &self.message
101    }
102
103    /// Returns the error context entries.
104    pub fn context(&self) -> &[(&'static str, String)] {
105        &self.context
106    }
107
108    /// Adds a context entry to the error.
109    pub fn with_context(mut self, key: &'static str, value: impl ToString) -> Self {
110        self.context.push((key, value.to_string()));
111        self
112    }
113
114    /// Attaches a error source.
115    pub fn set_source(mut self, source: impl IntoErrorSource) -> Self {
116        debug_assert!(self.source.is_none(), "source already set");
117        self.source = Some(source.into_error_source());
118        self
119    }
120
121    /// Returns the source error, if any.
122    pub fn source(&self) -> Option<&ErrorSource> {
123        self.source.as_ref()
124    }
125}
126
127impl fmt::Display for Error {
128    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129        write!(f, "{}: {}", self.kind, self.message)?;
130
131        if !self.context.is_empty() {
132            f.write_str(" { ")?;
133            for (index, (key, value)) in self.context.iter().enumerate() {
134                if index > 0 {
135                    f.write_str(", ")?;
136                }
137                write!(f, "{key}={value}")?;
138            }
139            f.write_str(" }")?;
140        }
141
142        Ok(())
143    }
144}
145
146impl fmt::Debug for Error {
147    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148        writeln!(f, "{}: {}", self.kind, self.message)?;
149        if !self.context.is_empty() {
150            writeln!(f, "Context:")?;
151            for (key, value) in &self.context {
152                writeln!(f, "    {key}: {value}")?;
153            }
154        }
155        if let Some(source) = &self.source {
156            writeln!(f, "Source: {source}")?;
157        }
158        Ok(())
159    }
160}
161
162impl error::Error for Error {
163    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
164        self.source.as_ref().map(|err| err.0.as_ref())
165    }
166}
167
168impl From<eventsrc::ProtocolError> for Error {
169    fn from(err: eventsrc::ProtocolError) -> Self {
170        Self {
171            kind: ErrorKind::Protocol,
172            message: Cow::Owned(err.to_string()),
173            context: Vec::new(),
174            source: None,
175        }
176    }
177}
178
179impl<E> From<eventsrc::StreamError<E>> for Error
180where
181    E: error::Error + Send + Sync + 'static,
182{
183    fn from(err: eventsrc::StreamError<E>) -> Self {
184        match err {
185            eventsrc::StreamError::Source(e) => Self {
186                kind: ErrorKind::InvalidResponse,
187                message: "invalid response body stream error".into(),
188                context: Vec::new(),
189                source: Some(anyhow::Error::new(e).into_error_source()),
190            },
191            eventsrc::StreamError::Protocol(e) => Self {
192                kind: ErrorKind::Protocol,
193                message: Cow::Owned(e.to_string()),
194                context: Vec::new(),
195                source: None,
196            },
197        }
198    }
199}