Skip to main content

lapin/
error.rs

1use crate::{
2    ChannelState, ConnectionState, notifier::Notifier, protocol::AMQPError, types::ChannelId,
3};
4use amq_protocol::{
5    frame::{GenError, ParserError, ProtocolVersion},
6    protocol::AMQPErrorKind,
7};
8use async_rs::{Runtime, traits::*};
9use std::{
10    error, fmt, io,
11    panic::{RefUnwindSafe, UnwindSafe},
12    sync::Arc,
13};
14
15/// A std Result with a lapin::Error error type
16pub type Result<T> = std::result::Result<T, Error>;
17
18/// The error that can be returned in this crate.
19#[derive(Clone, Debug)]
20pub struct Error {
21    kind: ErrorKind,
22    notifier: Option<Notifier>,
23}
24
25/// The type of error that can be returned in this crate.
26///
27/// Even though we expose the complete enumeration of possible error variants, it is not
28/// considered stable to exhaustively match on this enumeration: do it at your own risk.
29#[derive(Clone, Debug)]
30#[non_exhaustive]
31pub enum ErrorKind {
32    /// The maximum number of channels allowed on this connection has been reached.
33    ChannelsLimitReached,
34    /// The server only supports an AMQP version that this client does not speak.
35    InvalidProtocolVersion(ProtocolVersion),
36
37    /// An operation was attempted on a channel number that does not exist.
38    InvalidChannel(ChannelId),
39    /// An operation was attempted while the channel was in an incompatible state.
40    InvalidChannelState(ChannelState, &'static str),
41    /// An operation was attempted while the connection was in an incompatible state.
42    InvalidConnectionState(ConnectionState),
43
44    /// An underlying IO error occurred (e.g. connection reset, broken pipe).
45    IOError(Arc<io::Error>),
46    /// The async runtime was shut down while an IO operation was in progress.
47    RuntimeShutdownError(Arc<io::Error>),
48    /// The AMQP frame parser encountered malformed data.
49    ParsingError(ParserError),
50    /// The broker sent an AMQP error (channel or connection level).
51    ProtocolError(AMQPError),
52    /// An AMQP frame could not be serialised.
53    SerialisationError(Arc<GenError>),
54    /// The authentication provider returned an error.
55    AuthProviderError(String),
56    /// A [`crate::PublisherConfirm`] future was polled after it had already resolved.
57    FutureCompleted,
58    /// No default async runtime is available (no runtime feature flag was enabled).
59    NoDefaultRuntime,
60
61    /// The broker did not send a heartbeat within the negotiated timeout.
62    MissingHeartbeatError,
63}
64
65impl Error {
66    pub(crate) fn other<E: Into<Box<dyn error::Error + Send + Sync>>>(error: E) -> Self {
67        io::Error::other(error).into()
68    }
69
70    pub(crate) fn io<RK: RuntimeKit>(error: io::Error, rt: &Runtime<RK>) -> Self {
71        if rt.is_runtime_shutdown_error(&error) {
72            ErrorKind::RuntimeShutdownError(Arc::new(error)).into()
73        } else {
74            error.into()
75        }
76    }
77
78    /// Return the specific error kind.
79    #[must_use]
80    pub fn kind(&self) -> &ErrorKind {
81        &self.kind
82    }
83
84    pub(crate) fn notifier(&self) -> Option<Notifier> {
85        self.notifier.clone()
86    }
87
88    pub(crate) fn with_notifier(mut self, notifier: Option<Notifier>) -> Self {
89        self.notifier = notifier;
90        self
91    }
92
93    /// Returns `true` if this is an IO error with `WouldBlock` kind.
94    #[must_use]
95    pub fn wouldblock(&self) -> bool {
96        matches!(self.kind(), ErrorKind::IOError(e) if e.kind() == io::ErrorKind::WouldBlock)
97    }
98
99    /// Returns `true` if this is an IO error with `Interrupted` kind.
100    #[must_use]
101    pub fn interrupted(&self) -> bool {
102        matches!(self.kind(), ErrorKind::IOError(e) if e.kind() == io::ErrorKind::Interrupted)
103    }
104
105    /// Returns `true` if this is an [`ErrorKind::IOError`] or
106    /// [`ErrorKind::RuntimeShutdownError`].
107    #[must_use]
108    pub fn is_io_error(&self) -> bool {
109        matches!(self.kind(), ErrorKind::IOError(_)) || self.is_runtime_shutdown_error()
110    }
111
112    /// Returns `true` if this is an [`ErrorKind::RuntimeShutdownError`].
113    #[must_use]
114    pub fn is_runtime_shutdown_error(&self) -> bool {
115        matches!(self.kind(), ErrorKind::RuntimeShutdownError(_))
116    }
117
118    /// Returns `true` if this is an [`ErrorKind::ProtocolError`].
119    #[must_use]
120    pub fn is_amqp_error(&self) -> bool {
121        matches!(self.kind(), ErrorKind::ProtocolError(_))
122    }
123
124    /// Returns `true` if this is a channel-level (soft) AMQP protocol error.
125    #[must_use]
126    pub fn is_amqp_soft_error(&self) -> bool {
127        matches!(self.kind(), ErrorKind::ProtocolError(e) if matches!(e.kind(), AMQPErrorKind::Soft(_)))
128    }
129
130    /// Returns `true` if this is a connection-level (hard) AMQP protocol error.
131    #[must_use]
132    pub fn is_amqp_hard_error(&self) -> bool {
133        matches!(self.kind(), ErrorKind::ProtocolError(e) if matches!(e.kind(), AMQPErrorKind::Hard(_)))
134    }
135
136    /// Returns `true` if automatic recovery can be attempted for this error.
137    ///
138    /// Used internally by the auto-recovery logic. Requires
139    /// [`ConnectionProperties::enable_auto_recover`] to be set.
140    ///
141    /// [`ConnectionProperties::enable_auto_recover`]: crate::ConnectionProperties::enable_auto_recover
142    #[must_use]
143    pub fn can_be_recovered(&self) -> bool {
144        match self.kind() {
145            ErrorKind::ChannelsLimitReached => false,
146            ErrorKind::InvalidProtocolVersion(_) => false,
147
148            ErrorKind::InvalidChannel(_) => true,
149            ErrorKind::InvalidChannelState(..) => true,
150            ErrorKind::InvalidConnectionState(_) => true,
151
152            ErrorKind::IOError(_) => true,
153            ErrorKind::RuntimeShutdownError(_) => false,
154            ErrorKind::ParsingError(_) => false,
155            ErrorKind::ProtocolError(_) => true,
156            ErrorKind::SerialisationError(_) => false,
157            ErrorKind::AuthProviderError(_) => false,
158            ErrorKind::FutureCompleted => false,
159            ErrorKind::NoDefaultRuntime => false,
160
161            ErrorKind::MissingHeartbeatError => true,
162        }
163    }
164}
165
166// io::Error can contain Box<dyn Error + Send + Sync>, which opts out of RefUnwindSafe
167// even though the data is behind Arc (immutable shared reference). Error values carry
168// no interior mutability of their own; a panic through code holding an Error cannot
169// corrupt any invariant.
170impl UnwindSafe for Error {}
171impl RefUnwindSafe for Error {}
172
173impl fmt::Display for Error {
174    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175        match self.kind() {
176            ErrorKind::ChannelsLimitReached => write!(
177                f,
178                "the maximum number of channels for this connection has been reached"
179            ),
180            ErrorKind::InvalidProtocolVersion(version) => {
181                write!(f, "the server only supports AMQP {version}")
182            }
183
184            ErrorKind::InvalidChannel(channel) => write!(f, "invalid channel: {channel}"),
185            ErrorKind::InvalidChannelState(state, context) => {
186                write!(f, "invalid channel state: {state:?} ({context})")
187            }
188            ErrorKind::InvalidConnectionState(state) => {
189                write!(f, "invalid connection state: {state:?}")
190            }
191
192            ErrorKind::IOError(e) => write!(f, "IO error: {e}"),
193            ErrorKind::RuntimeShutdownError(e) => write!(f, "runtime shutdown error: {e}"),
194            ErrorKind::ParsingError(e) => write!(f, "failed to parse: {e}"),
195            ErrorKind::ProtocolError(e) => write!(f, "protocol error: {e}"),
196            ErrorKind::SerialisationError(e) => write!(f, "failed to serialise: {e}"),
197            ErrorKind::AuthProviderError(e) => write!(f, "failure during authentication: {e}"),
198            ErrorKind::FutureCompleted => write!(f, "future polled after completion"),
199            ErrorKind::NoDefaultRuntime => write!(f, "no default configured runtime"),
200
201            ErrorKind::MissingHeartbeatError => {
202                write!(f, "no heartbeat received from server for too long")
203            }
204        }
205    }
206}
207
208impl error::Error for Error {
209    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
210        match self.kind() {
211            ErrorKind::IOError(e) => Some(&**e),
212            ErrorKind::RuntimeShutdownError(e) => Some(&**e),
213            ErrorKind::ParsingError(e) => Some(e),
214            ErrorKind::ProtocolError(e) => Some(e),
215            ErrorKind::SerialisationError(e) => Some(&**e),
216            _ => None,
217        }
218    }
219}
220
221impl From<ErrorKind> for Error {
222    fn from(kind: ErrorKind) -> Self {
223        Self {
224            kind,
225            notifier: None,
226        }
227    }
228}
229
230impl From<io::Error> for Error {
231    fn from(other: io::Error) -> Self {
232        ErrorKind::IOError(Arc::new(other)).into()
233    }
234}
235
236impl PartialEq for Error {
237    fn eq(&self, other: &Self) -> bool {
238        use ErrorKind::*;
239
240        match (self.kind(), other.kind()) {
241            (ChannelsLimitReached, ChannelsLimitReached) => true,
242            (InvalidProtocolVersion(left_inner), InvalidProtocolVersion(right_version)) => {
243                left_inner == right_version
244            }
245
246            (InvalidChannel(left_inner), InvalidChannel(right_inner)) => left_inner == right_inner,
247            (
248                InvalidChannelState(left_inner, left_context),
249                InvalidChannelState(right_inner, right_context),
250            ) => left_inner == right_inner && left_context == right_context,
251            (InvalidConnectionState(left_inner), InvalidConnectionState(right_inner)) => {
252                left_inner == right_inner
253            }
254
255            (IOError(_), IOError(_)) => false,
256            (RuntimeShutdownError(_), RuntimeShutdownError(_)) => false,
257            (ParsingError(left_inner), ParsingError(right_inner)) => left_inner == right_inner,
258            (ProtocolError(left_inner), ProtocolError(right_inner)) => left_inner == right_inner,
259            (SerialisationError(_), SerialisationError(_)) => false,
260            (FutureCompleted, FutureCompleted) => true,
261            (NoDefaultRuntime, NoDefaultRuntime) => true,
262
263            _ => false,
264        }
265    }
266}