use crate::common::error::FlareError;
use std::sync::{Arc, Mutex};
#[derive(Debug, Clone)]
pub enum ConnectionEvent {
Connected,
Disconnected(String),
Message(Vec<u8>),
Error(FlareError),
}
impl ConnectionEvent {
pub fn is_connected(&self) -> bool {
matches!(self, Self::Connected)
}
pub fn is_disconnected(&self) -> bool {
matches!(self, Self::Disconnected(_))
}
pub fn is_message(&self) -> bool {
matches!(self, Self::Message(_))
}
pub fn is_error(&self) -> bool {
matches!(self, Self::Error(_))
}
pub fn disconnect_reason(&self) -> Option<&str> {
match self {
Self::Disconnected(reason) => Some(reason),
_ => None,
}
}
pub fn message_data(&self) -> Option<&[u8]> {
match self {
Self::Message(data) => Some(data),
_ => None,
}
}
pub fn error(&self) -> Option<&FlareError> {
match self {
Self::Error(err) => Some(err),
_ => None,
}
}
}
pub trait ConnectionObserver: Send + Sync {
fn on_event(&self, event: &ConnectionEvent);
}
pub type ArcObserver = Arc<dyn ConnectionObserver>;
pub(crate) fn notify_observers(
observers_arc: &Arc<Mutex<Vec<ArcObserver>>>,
event: &ConnectionEvent,
lock_name: &str,
) {
let observers = match observers_arc.lock() {
Ok(observers) => observers.clone(),
Err(e) => {
tracing::warn!("{lock_name} lock poisoned: {e}");
return;
}
};
for observer in observers {
observer.on_event(event);
}
}
pub(crate) fn notify_observers_and_clear(
observers_arc: &Arc<Mutex<Vec<ArcObserver>>>,
event: &ConnectionEvent,
lock_name: &str,
) {
let observers = match observers_arc.lock() {
Ok(mut observers) => std::mem::take(&mut *observers),
Err(e) => {
tracing::warn!("{lock_name} lock poisoned: {e}");
return;
}
};
for observer in observers {
observer.on_event(event);
}
}
pub struct NoOpObserver;
impl ConnectionObserver for NoOpObserver {
fn on_event(&self, _event: &ConnectionEvent) {
}
}
impl NoOpObserver {
#[allow(clippy::new_ret_no_self)]
pub fn new() -> ArcObserver {
Arc::new(Self)
}
}
pub struct LoggingObserver {
prefix: String,
}
impl LoggingObserver {
#[allow(clippy::new_ret_no_self)]
pub fn new(prefix: impl Into<String>) -> ArcObserver {
Arc::new(Self {
prefix: prefix.into(),
})
}
}
impl ConnectionObserver for LoggingObserver {
fn on_event(&self, event: &ConnectionEvent) {
match event {
ConnectionEvent::Connected => {
tracing::info!("[{}] Connection established", self.prefix);
}
ConnectionEvent::Disconnected(reason) => {
tracing::info!("[{}] Connection disconnected: {}", self.prefix, reason);
}
ConnectionEvent::Message(data) => {
tracing::debug!("[{}] Message received: {} bytes", self.prefix, data.len());
}
ConnectionEvent::Error(err) => {
tracing::error!("[{}] Connection error: {:?}", self.prefix, err);
}
}
}
}
pub struct CompositeObserver {
observers: Vec<ArcObserver>,
}
impl CompositeObserver {
pub fn new() -> Self {
Self {
observers: Vec::new(),
}
}
pub fn add(&mut self, observer: ArcObserver) {
self.observers.push(observer);
}
pub fn into_arc(self) -> ArcObserver {
Arc::new(self)
}
}
impl ConnectionObserver for CompositeObserver {
fn on_event(&self, event: &ConnectionEvent) {
for observer in &self.observers {
observer.on_event(event);
}
}
}
impl Default for CompositeObserver {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
struct CountingObserver {
calls: Arc<AtomicUsize>,
drops: Arc<AtomicUsize>,
}
impl ConnectionObserver for CountingObserver {
fn on_event(&self, _event: &ConnectionEvent) {
self.calls.fetch_add(1, Ordering::SeqCst);
}
}
impl Drop for CountingObserver {
fn drop(&mut self) {
self.drops.fetch_add(1, Ordering::SeqCst);
}
}
#[test]
fn terminal_notify_clears_and_drops_observers() {
let calls = Arc::new(AtomicUsize::new(0));
let drops = Arc::new(AtomicUsize::new(0));
let observers = Arc::new(Mutex::new(Vec::<ArcObserver>::new()));
observers.lock().unwrap().push(Arc::new(CountingObserver {
calls: Arc::clone(&calls),
drops: Arc::clone(&drops),
}));
notify_observers_and_clear(
&observers,
&ConnectionEvent::Disconnected("test".to_string()),
"test observers",
);
assert_eq!(calls.load(Ordering::SeqCst), 1);
assert_eq!(drops.load(Ordering::SeqCst), 1);
assert!(observers.lock().unwrap().is_empty());
}
#[test]
fn non_terminal_notify_keeps_observers_registered() {
let calls = Arc::new(AtomicUsize::new(0));
let drops = Arc::new(AtomicUsize::new(0));
let observers = Arc::new(Mutex::new(Vec::<ArcObserver>::new()));
observers.lock().unwrap().push(Arc::new(CountingObserver {
calls: Arc::clone(&calls),
drops: Arc::clone(&drops),
}));
notify_observers(&observers, &ConnectionEvent::Connected, "test observers");
assert_eq!(calls.load(Ordering::SeqCst), 1);
assert_eq!(drops.load(Ordering::SeqCst), 0);
assert_eq!(observers.lock().unwrap().len(), 1);
}
}