use std::{
any::{TypeId, type_name},
marker::PhantomData,
};
use crate::Conn;
#[must_use = "dropping an erased connection abandons the PostgreSQL session"]
#[derive(Debug)]
pub struct ErasedConn<S> {
transport: Option<S>,
phase: TypeId,
phase_name: &'static str,
cleanliness: TypeId,
cleanliness_name: &'static str,
}
impl<S> ErasedConn<S> {
#[must_use]
pub const fn phase_name(&self) -> &'static str {
self.phase_name
}
#[must_use]
pub const fn cleanliness_name(&self) -> &'static str {
self.cleanliness_name
}
#[must_use]
pub fn phase_is<P: 'static>(&self) -> bool {
self.phase == TypeId::of::<P>()
}
#[must_use]
pub fn cleanliness_is<C: 'static>(&self) -> bool {
self.cleanliness == TypeId::of::<C>()
}
pub fn try_reenter<P: 'static, C: 'static>(mut self) -> Result<Conn<S, P, C>, Self> {
if !self.phase_is::<P>() || !self.cleanliness_is::<C>() {
return Err(self);
}
Ok(Conn {
transport: self.transport.take(),
_state: PhantomData,
})
}
pub fn map_transport<T>(mut self, map: impl FnOnce(S) -> T) -> ErasedConn<T> {
ErasedConn {
transport: Some(map(self
.transport
.take()
.expect("live erased connection has a transport"))),
phase: self.phase,
phase_name: self.phase_name,
cleanliness: self.cleanliness,
cleanliness_name: self.cleanliness_name,
}
}
pub fn into_transport(mut self) -> S {
self.transport
.take()
.expect("live erased connection has a transport")
}
}
impl<S, P: 'static, C: 'static> Conn<S, P, C> {
pub fn erase(mut self) -> ErasedConn<S> {
ErasedConn {
transport: self.transport.take(),
phase: TypeId::of::<P>(),
phase_name: type_name::<P>(),
cleanliness: TypeId::of::<C>(),
cleanliness_name: type_name::<C>(),
}
}
}
#[cfg(debug_assertions)]
impl<S> Drop for ErasedConn<S> {
fn drop(&mut self) {
assert!(
self.transport.is_none() || std::thread::panicking(),
"live erased PostgreSQL connection dropped; re-enter or extract its transport"
);
}
}
#[cfg(test)]
mod tests {
use crate::{Dirty, Pristine, auth::Ready, session::Building};
use super::*;
#[test]
fn exact_state_can_be_erased_and_reentered() {
let ready: Conn<_, Ready, Pristine> = Conn::new(42_u8).transition();
let erased = ready.erase();
assert!(erased.phase_is::<Ready>());
assert!(erased.cleanliness_is::<Pristine>());
let ready = erased
.try_reenter::<Ready, Pristine>()
.expect("exact state identities match");
assert_eq!(ready.into_transport(), 42);
}
#[test]
fn failed_reentry_preserves_the_erased_connection() {
let building: Conn<_, Building, Dirty> = Conn::new(42_u8).transition();
let erased = building.erase();
let erased = erased
.try_reenter::<Ready, Dirty>()
.expect_err("wrong phase must not re-enter");
let erased = erased
.try_reenter::<Building, Pristine>()
.expect_err("wrong cleanliness must not re-enter");
let building = erased
.try_reenter::<Building, Dirty>()
.expect("both exact identities match");
assert_eq!(building.into_transport(), 42);
}
#[test]
fn transport_mapping_does_not_change_erased_state() {
let ready: Conn<_, Ready, Pristine> = Conn::new(42_u8).transition();
let erased = ready.erase().map_transport(u16::from);
let ready = erased
.try_reenter::<Ready, Pristine>()
.expect("mapping retained exact identities");
assert_eq!(ready.into_transport(), 42_u16);
}
}