use async_trait::async_trait;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PoolEvent {
ConnectionAcquired {
connection_id: String,
timestamp: DateTime<Utc>,
},
ConnectionReturned {
connection_id: String,
timestamp: DateTime<Utc>,
},
ConnectionCreated {
connection_id: String,
timestamp: DateTime<Utc>,
},
ConnectionClosed {
connection_id: String,
reason: String,
timestamp: DateTime<Utc>,
},
ConnectionTestFailed {
connection_id: String,
error: String,
timestamp: DateTime<Utc>,
},
ConnectionInvalidated {
connection_id: String,
reason: String,
timestamp: DateTime<Utc>,
},
ConnectionSoftInvalidated {
connection_id: String,
timestamp: DateTime<Utc>,
},
ConnectionReset {
connection_id: String,
timestamp: DateTime<Utc>,
},
}
impl PoolEvent {
pub fn connection_acquired(connection_id: String) -> Self {
Self::ConnectionAcquired {
connection_id,
timestamp: Utc::now(),
}
}
pub fn connection_returned(connection_id: String) -> Self {
Self::ConnectionReturned {
connection_id,
timestamp: Utc::now(),
}
}
pub fn connection_created(connection_id: String) -> Self {
Self::ConnectionCreated {
connection_id,
timestamp: Utc::now(),
}
}
pub fn connection_closed(connection_id: String, reason: String) -> Self {
Self::ConnectionClosed {
connection_id,
reason,
timestamp: Utc::now(),
}
}
pub fn connection_test_failed(connection_id: String, error: String) -> Self {
Self::ConnectionTestFailed {
connection_id,
error,
timestamp: Utc::now(),
}
}
pub fn connection_invalidated(connection_id: String, reason: String) -> Self {
Self::ConnectionInvalidated {
connection_id,
reason,
timestamp: Utc::now(),
}
}
pub fn connection_soft_invalidated(connection_id: String) -> Self {
Self::ConnectionSoftInvalidated {
connection_id,
timestamp: Utc::now(),
}
}
pub fn connection_reset(connection_id: String) -> Self {
Self::ConnectionReset {
connection_id,
timestamp: Utc::now(),
}
}
}
#[async_trait]
pub trait PoolEventListener: Send + Sync {
async fn on_event(&self, event: PoolEvent);
}
pub struct EventLogger;
#[async_trait]
impl PoolEventListener for EventLogger {
async fn on_event(&self, event: PoolEvent) {
match event {
PoolEvent::ConnectionAcquired { connection_id, .. } => {
println!("Connection acquired: {}", connection_id);
}
PoolEvent::ConnectionReturned { connection_id, .. } => {
println!("Connection returned: {}", connection_id);
}
PoolEvent::ConnectionCreated { connection_id, .. } => {
println!("Connection created: {}", connection_id);
}
PoolEvent::ConnectionClosed {
connection_id,
reason,
..
} => {
println!("Connection closed: {} (reason: {})", connection_id, reason);
}
PoolEvent::ConnectionTestFailed {
connection_id,
error,
..
} => {
println!(
"Connection test failed: {} (error: {})",
connection_id, error
);
}
PoolEvent::ConnectionInvalidated {
connection_id,
reason,
..
} => {
println!(
"Connection invalidated: {} (reason: {})",
connection_id, reason
);
}
PoolEvent::ConnectionSoftInvalidated { connection_id, .. } => {
println!("Connection soft invalidated: {}", connection_id);
}
PoolEvent::ConnectionReset { connection_id, .. } => {
println!("Connection reset: {}", connection_id);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn connection_acquired_records_id_and_current_timestamp() {
let before = Utc::now();
let event = PoolEvent::connection_acquired("conn-1".to_string());
let after = Utc::now();
assert!(matches!(
event,
PoolEvent::ConnectionAcquired {
connection_id,
timestamp
} if connection_id == "conn-1" && before <= timestamp && timestamp <= after
));
}
#[test]
fn connection_returned_records_id_and_current_timestamp() {
let before = Utc::now();
let event = PoolEvent::connection_returned("conn-2".to_string());
let after = Utc::now();
assert!(matches!(
event,
PoolEvent::ConnectionReturned {
connection_id,
timestamp
} if connection_id == "conn-2" && before <= timestamp && timestamp <= after
));
}
#[test]
fn connection_created_records_id_and_current_timestamp() {
let before = Utc::now();
let event = PoolEvent::connection_created("conn-3".to_string());
let after = Utc::now();
assert!(matches!(
event,
PoolEvent::ConnectionCreated {
connection_id,
timestamp
} if connection_id == "conn-3" && before <= timestamp && timestamp <= after
));
}
#[test]
fn connection_closed_records_id_reason_and_current_timestamp() {
let before = Utc::now();
let event = PoolEvent::connection_closed("conn-7".to_string(), "idle timeout".to_string());
let after = Utc::now();
assert!(matches!(
event,
PoolEvent::ConnectionClosed {
connection_id,
reason,
timestamp
} if connection_id == "conn-7"
&& reason == "idle timeout"
&& before <= timestamp
&& timestamp <= after
));
}
#[test]
fn connection_test_failed_records_id_error_and_current_timestamp() {
let before = Utc::now();
let event =
PoolEvent::connection_test_failed("conn-4".to_string(), "ping failed".to_string());
let after = Utc::now();
assert!(matches!(
event,
PoolEvent::ConnectionTestFailed {
connection_id,
error,
timestamp
} if connection_id == "conn-4"
&& error == "ping failed"
&& before <= timestamp
&& timestamp <= after
));
}
#[test]
fn connection_invalidated_records_id_reason_and_current_timestamp() {
let before = Utc::now();
let event = PoolEvent::connection_invalidated("conn-5".to_string(), "broken".to_string());
let after = Utc::now();
assert!(matches!(
event,
PoolEvent::ConnectionInvalidated {
connection_id,
reason,
timestamp
} if connection_id == "conn-5"
&& reason == "broken"
&& before <= timestamp
&& timestamp <= after
));
}
#[test]
fn connection_soft_invalidated_records_id_and_current_timestamp() {
let before = Utc::now();
let event = PoolEvent::connection_soft_invalidated("conn-6".to_string());
let after = Utc::now();
assert!(matches!(
event,
PoolEvent::ConnectionSoftInvalidated {
connection_id,
timestamp
} if connection_id == "conn-6" && before <= timestamp && timestamp <= after
));
}
#[test]
fn connection_reset_records_id_and_current_timestamp() {
let before = Utc::now();
let event = PoolEvent::connection_reset("conn-8".to_string());
let after = Utc::now();
assert!(matches!(
event,
PoolEvent::ConnectionReset {
connection_id,
timestamp
} if connection_id == "conn-8" && before <= timestamp && timestamp <= after
));
}
#[tokio::test]
async fn event_logger_handles_every_pool_event_variant() {
let logger = EventLogger;
let events = [
PoolEvent::connection_acquired("conn-1".to_string()),
PoolEvent::connection_returned("conn-2".to_string()),
PoolEvent::connection_created("conn-3".to_string()),
PoolEvent::connection_closed("conn-4".to_string(), "idle timeout".to_string()),
PoolEvent::connection_test_failed("conn-5".to_string(), "ping failed".to_string()),
PoolEvent::connection_invalidated("conn-6".to_string(), "broken".to_string()),
PoolEvent::connection_soft_invalidated("conn-7".to_string()),
PoolEvent::connection_reset("conn-8".to_string()),
];
assert_eq!(events.len(), 8);
for event in events {
logger.on_event(event).await;
}
}
}