use std::sync::{Arc, Mutex};
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ConnectionTermination {
IncomingClosed,
LocalShutdown,
ForegroundError(crate::Error),
TransportError(crate::Error),
HandlerError(crate::Error),
Dropped,
}
impl ConnectionTermination {
#[must_use]
pub fn error(&self) -> Option<&crate::Error> {
match self {
Self::ForegroundError(error)
| Self::TransportError(error)
| Self::HandlerError(error) => Some(error),
Self::IncomingClosed | Self::LocalShutdown | Self::Dropped => None,
}
}
#[must_use]
pub fn is_clean(&self) -> bool {
matches!(self, Self::IncomingClosed | Self::LocalShutdown)
}
pub(super) fn kind(&self) -> &'static str {
match self {
Self::IncomingClosed => "incoming_closed",
Self::LocalShutdown => "local_shutdown",
Self::ForegroundError(_) => "foreground_error",
Self::TransportError(_) => "transport_error",
Self::HandlerError(_) => "handler_error",
Self::Dropped => "dropped",
}
}
}
pub const CONNECTION_TERMINATED_REASON: &str = "connection_terminated";
#[must_use]
pub fn is_connection_terminated(error: &crate::Error) -> bool {
error
.data
.as_ref()
.and_then(|data| data.get("reason"))
.and_then(serde_json::Value::as_str)
== Some(CONNECTION_TERMINATED_REASON)
}
pub(super) fn connection_terminated_error(
method: &str,
termination: &ConnectionTermination,
) -> crate::Error {
let mut error = crate::Error::internal_error();
error.message = "Connection terminated".to_string();
error.data(serde_json::json!({
"reason": CONNECTION_TERMINATED_REASON,
"method": method,
"termination": termination.kind(),
}))
}
#[derive(Clone, Copy, Debug)]
pub(super) enum TerminationSource {
Transport,
Handler,
Foreground,
}
#[derive(Clone, Default)]
pub(super) struct TerminationSources {
errors: Arc<Mutex<Vec<(TerminationSource, crate::Error)>>>,
}
impl TerminationSources {
pub(super) fn record<T>(&self, source: TerminationSource, result: &Result<T, crate::Error>) {
if let Err(error) = result {
self.errors
.lock()
.expect("termination sources mutex poisoned")
.push((source, error.clone()));
}
}
pub(super) fn classify(&self, error: crate::Error) -> ConnectionTermination {
let source = {
let errors = self
.errors
.lock()
.expect("termination sources mutex poisoned");
errors
.iter()
.find(|(_, recorded)| *recorded == error)
.or_else(|| errors.first())
.map_or(TerminationSource::Handler, |(source, _)| *source)
};
match source {
TerminationSource::Transport => ConnectionTermination::TransportError(error),
TerminationSource::Handler => ConnectionTermination::HandlerError(error),
TerminationSource::Foreground => ConnectionTermination::ForegroundError(error),
}
}
}