Skip to main content

pg_proto/
erased.rs

1//! Exact runtime erasure for storage and forwarding boundaries.
2
3use std::{
4    any::{TypeId, type_name},
5    marker::PhantomData,
6};
7
8use crate::Conn;
9
10/// A connection whose phase and cleanliness markers are retained as exact
11/// runtime identities rather than generic parameters.
12#[must_use = "dropping an erased connection abandons the PostgreSQL session"]
13#[derive(Debug)]
14pub struct ErasedConn<S> {
15    transport: Option<S>,
16    phase: TypeId,
17    phase_name: &'static str,
18    cleanliness: TypeId,
19    cleanliness_name: &'static str,
20}
21
22impl<S> ErasedConn<S> {
23    /// Reports the erased phase marker for diagnostics.
24    #[must_use]
25    pub const fn phase_name(&self) -> &'static str {
26        self.phase_name
27    }
28
29    /// Reports the erased cleanliness marker for diagnostics.
30    #[must_use]
31    pub const fn cleanliness_name(&self) -> &'static str {
32        self.cleanliness_name
33    }
34
35    /// Checks whether the exact requested phase marker was erased.
36    #[must_use]
37    pub fn phase_is<P: 'static>(&self) -> bool {
38        self.phase == TypeId::of::<P>()
39    }
40
41    /// Checks whether the exact requested cleanliness marker was erased.
42    #[must_use]
43    pub fn cleanliness_is<C: 'static>(&self) -> bool {
44        self.cleanliness == TypeId::of::<C>()
45    }
46
47    /// Re-enters the typed API only when both runtime identities exactly match.
48    ///
49    /// A failed attempt returns the unchanged erased connection.
50    ///
51    /// # Errors
52    ///
53    /// Returns the unchanged connection when either requested marker differs.
54    pub fn try_reenter<P: 'static, C: 'static>(mut self) -> Result<Conn<S, P, C>, Self> {
55        if !self.phase_is::<P>() || !self.cleanliness_is::<C>() {
56            return Err(self);
57        }
58        Ok(Conn {
59            transport: self.transport.take(),
60            _state: PhantomData,
61        })
62    }
63
64    /// Changes only the transport representation while retaining exact state
65    /// identities.
66    ///
67    /// # Panics
68    ///
69    /// Panics only if an earlier internal operation has moved the transport.
70    pub fn map_transport<T>(mut self, map: impl FnOnce(S) -> T) -> ErasedConn<T> {
71        ErasedConn {
72            transport: Some(map(self
73                .transport
74                .take()
75                .expect("live erased connection has a transport"))),
76            phase: self.phase,
77            phase_name: self.phase_name,
78            cleanliness: self.cleanliness,
79            cleanliness_name: self.cleanliness_name,
80        }
81    }
82
83    /// Irreversibly leaves state tracking and returns the underlying transport.
84    ///
85    /// # Panics
86    ///
87    /// Panics only if an earlier internal operation has moved the transport.
88    pub fn into_transport(mut self) -> S {
89        self.transport
90            .take()
91            .expect("live erased connection has a transport")
92    }
93}
94
95impl<S, P: 'static, C: 'static> Conn<S, P, C> {
96    /// Erases monomorphised state markers while retaining their exact runtime
97    /// identities for checked re-entry.
98    pub fn erase(mut self) -> ErasedConn<S> {
99        ErasedConn {
100            transport: self.transport.take(),
101            phase: TypeId::of::<P>(),
102            phase_name: type_name::<P>(),
103            cleanliness: TypeId::of::<C>(),
104            cleanliness_name: type_name::<C>(),
105        }
106    }
107}
108
109#[cfg(debug_assertions)]
110impl<S> Drop for ErasedConn<S> {
111    fn drop(&mut self) {
112        assert!(
113            self.transport.is_none() || std::thread::panicking(),
114            "live erased PostgreSQL connection dropped; re-enter or extract its transport"
115        );
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use crate::{Dirty, Pristine, auth::Ready, session::Building};
122
123    use super::*;
124
125    #[test]
126    fn exact_state_can_be_erased_and_reentered() {
127        let ready: Conn<_, Ready, Pristine> = Conn::new(42_u8).transition();
128        let erased = ready.erase();
129        assert!(erased.phase_is::<Ready>());
130        assert!(erased.cleanliness_is::<Pristine>());
131
132        let ready = erased
133            .try_reenter::<Ready, Pristine>()
134            .expect("exact state identities match");
135        assert_eq!(ready.into_transport(), 42);
136    }
137
138    #[test]
139    fn failed_reentry_preserves_the_erased_connection() {
140        let building: Conn<_, Building, Dirty> = Conn::new(42_u8).transition();
141        let erased = building.erase();
142
143        let erased = erased
144            .try_reenter::<Ready, Dirty>()
145            .expect_err("wrong phase must not re-enter");
146        let erased = erased
147            .try_reenter::<Building, Pristine>()
148            .expect_err("wrong cleanliness must not re-enter");
149        let building = erased
150            .try_reenter::<Building, Dirty>()
151            .expect("both exact identities match");
152        assert_eq!(building.into_transport(), 42);
153    }
154
155    #[test]
156    fn transport_mapping_does_not_change_erased_state() {
157        let ready: Conn<_, Ready, Pristine> = Conn::new(42_u8).transition();
158        let erased = ready.erase().map_transport(u16::from);
159
160        let ready = erased
161            .try_reenter::<Ready, Pristine>()
162            .expect("mapping retained exact identities");
163        assert_eq!(ready.into_transport(), 42_u16);
164    }
165}