Skip to main content

mssql_client/
state.rs

1//! Connection state types for type-state pattern.
2//!
3//! The type-state pattern ensures at compile time that certain operations
4//! can only be performed when the connection is in the appropriate state.
5//!
6//! ## State Transitions
7//!
8//! ```text
9//! Disconnected -> Connected (via TCP connect)
10//! Connected -> Ready (via authentication)
11//! Ready -> InTransaction (via begin_transaction())
12//! InTransaction -> Ready (via commit() or rollback())
13//! ```
14//!
15//! Queries do not change the connection state: `query()` borrows the client
16//! mutably and returns an iterable result. The [`Streaming`] state type is
17//! declared for the planned socket-streaming work but no API currently
18//! transitions into it.
19
20use std::marker::PhantomData;
21
22/// Marker trait for connection states.
23///
24/// This trait is sealed to prevent external implementations,
25/// ensuring that only the states defined in this crate are valid.
26pub trait ConnectionState: private::Sealed {}
27
28/// Connection is not yet established.
29///
30/// In this state, only `connect()` can be called.
31pub struct Disconnected;
32
33/// TCP connection established, awaiting authentication.
34///
35/// In this intermediate state:
36/// - TCP connection is open
37/// - TLS negotiation may be in progress or complete
38/// - Login/authentication has not yet completed
39///
40/// This state is mostly internal; users typically go directly from
41/// `Disconnected` to `Ready` via `Client::connect()`.
42pub struct Connected;
43
44/// Connection is established and ready for queries.
45///
46/// In this state, queries can be executed and transactions can be started.
47pub struct Ready;
48
49/// Connection is in a transaction.
50///
51/// In this state, queries execute within the transaction context.
52/// The transaction must be explicitly committed or rolled back.
53pub struct InTransaction;
54
55/// Connection is actively streaming results.
56///
57/// In this state, the connection is processing a result set.
58/// No other operations can be performed until the stream is
59/// consumed or cancelled.
60pub struct Streaming;
61
62impl ConnectionState for Disconnected {}
63impl ConnectionState for Connected {}
64impl ConnectionState for Ready {}
65impl ConnectionState for InTransaction {}
66impl ConnectionState for Streaming {}
67
68mod private {
69    pub trait Sealed {}
70    impl Sealed for super::Disconnected {}
71    impl Sealed for super::Connected {}
72    impl Sealed for super::Ready {}
73    impl Sealed for super::InTransaction {}
74    impl Sealed for super::Streaming {}
75}
76
77/// Type-level state transition marker.
78///
79/// This is used internally to track state transitions at compile time.
80#[derive(Debug)]
81pub struct StateMarker<S: ConnectionState> {
82    _state: PhantomData<S>,
83}
84
85impl<S: ConnectionState> StateMarker<S> {
86    pub(crate) fn new() -> Self {
87        Self {
88            _state: PhantomData,
89        }
90    }
91}
92
93impl<S: ConnectionState> Default for StateMarker<S> {
94    fn default() -> Self {
95        Self::new()
96    }
97}
98
99impl<S: ConnectionState> Clone for StateMarker<S> {
100    fn clone(&self) -> Self {
101        *self
102    }
103}
104
105impl<S: ConnectionState> Copy for StateMarker<S> {}
106
107/// Internal protocol state for runtime management.
108///
109/// While connection states are tracked at compile-time via type-state,
110/// the protocol layer has runtime state that must be managed.
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112#[non_exhaustive]
113pub enum ProtocolState {
114    /// Awaiting response from server.
115    AwaitingResponse,
116    /// Processing token stream.
117    ProcessingTokens,
118    /// Draining remaining tokens after cancellation.
119    Draining,
120}
121
122impl Default for ProtocolState {
123    fn default() -> Self {
124        Self::AwaitingResponse
125    }
126}
127
128impl ProtocolState {
129    /// Check if the connection is in a usable state.
130    ///
131    /// Currently all protocol states are usable. This method exists for
132    /// forward compatibility if a broken/poisoned state is added later.
133    #[must_use]
134    pub fn is_usable(&self) -> bool {
135        // All current states are usable
136        matches!(
137            self,
138            Self::AwaitingResponse | Self::ProcessingTokens | Self::Draining
139        )
140    }
141
142    /// Check if the connection is actively processing.
143    #[must_use]
144    pub fn is_busy(&self) -> bool {
145        matches!(self, Self::ProcessingTokens | Self::Draining)
146    }
147}