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//! Ready -> Streaming (via query() that returns stream)
13//! InTransaction -> Ready (via commit() or rollback())
14//! InTransaction -> Streaming (via query() within transaction)
15//! Streaming -> Ready (via stream completion or cancellation)
16//! Streaming -> InTransaction (via stream completion within transaction)
17//! ```
18
19use std::marker::PhantomData;
20
21/// Marker trait for connection states.
22///
23/// This trait is sealed to prevent external implementations,
24/// ensuring that only the states defined in this crate are valid.
25pub trait ConnectionState: private::Sealed {}
26
27/// Connection is not yet established.
28///
29/// In this state, only `connect()` can be called.
30pub struct Disconnected;
31
32/// TCP connection established, awaiting authentication.
33///
34/// In this intermediate state:
35/// - TCP connection is open
36/// - TLS negotiation may be in progress or complete
37/// - Login/authentication has not yet completed
38///
39/// This state is mostly internal; users typically go directly from
40/// `Disconnected` to `Ready` via `Client::connect()`.
41pub struct Connected;
42
43/// Connection is established and ready for queries.
44///
45/// In this state, queries can be executed and transactions can be started.
46pub struct Ready;
47
48/// Connection is in a transaction.
49///
50/// In this state, queries execute within the transaction context.
51/// The transaction must be explicitly committed or rolled back.
52pub struct InTransaction;
53
54/// Connection is actively streaming results.
55///
56/// In this state, the connection is processing a result set.
57/// No other operations can be performed until the stream is
58/// consumed or cancelled.
59pub struct Streaming;
60
61impl ConnectionState for Disconnected {}
62impl ConnectionState for Connected {}
63impl ConnectionState for Ready {}
64impl ConnectionState for InTransaction {}
65impl ConnectionState for Streaming {}
66
67mod private {
68    pub trait Sealed {}
69    impl Sealed for super::Disconnected {}
70    impl Sealed for super::Connected {}
71    impl Sealed for super::Ready {}
72    impl Sealed for super::InTransaction {}
73    impl Sealed for super::Streaming {}
74}
75
76/// Type-level state transition marker.
77///
78/// This is used internally to track state transitions at compile time.
79#[derive(Debug)]
80pub struct StateMarker<S: ConnectionState> {
81    _state: PhantomData<S>,
82}
83
84impl<S: ConnectionState> StateMarker<S> {
85    pub(crate) fn new() -> Self {
86        Self {
87            _state: PhantomData,
88        }
89    }
90}
91
92impl<S: ConnectionState> Default for StateMarker<S> {
93    fn default() -> Self {
94        Self::new()
95    }
96}
97
98impl<S: ConnectionState> Clone for StateMarker<S> {
99    fn clone(&self) -> Self {
100        *self
101    }
102}
103
104impl<S: ConnectionState> Copy for StateMarker<S> {}
105
106/// Internal protocol state for runtime management.
107///
108/// While connection states are tracked at compile-time via type-state,
109/// the protocol layer has runtime state that must be managed.
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111pub enum ProtocolState {
112    /// Awaiting response from server.
113    AwaitingResponse,
114    /// Processing token stream.
115    ProcessingTokens,
116    /// Draining remaining tokens after cancellation.
117    Draining,
118    /// Connection is in a broken state due to protocol error.
119    Poisoned,
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    #[must_use]
131    pub fn is_usable(&self) -> bool {
132        !matches!(self, Self::Poisoned)
133    }
134
135    /// Check if the connection is actively processing.
136    #[must_use]
137    pub fn is_busy(&self) -> bool {
138        matches!(self, Self::ProcessingTokens | Self::Draining)
139    }
140}