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}