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}