redis_oxide/protocol/
mod.rs1pub mod resp2;
7pub mod resp2_optimized;
8pub mod resp3;
9
10pub use resp2::{RespDecoder, RespEncoder};
12pub use resp2_optimized::{OptimizedRespDecoder, OptimizedRespEncoder};
13pub use resp3::{Resp3Decoder, Resp3Encoder, Resp3Value};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
17pub enum ProtocolVersion {
18 #[default]
20 Resp2,
21 Resp3,
23}
24
25impl std::fmt::Display for ProtocolVersion {
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 match self {
28 Self::Resp2 => write!(f, "RESP2"),
29 Self::Resp3 => write!(f, "RESP3"),
30 }
31 }
32}
33
34#[derive(Debug, Clone)]
36pub struct ProtocolNegotiation {
37 pub version: ProtocolVersion,
39 pub capabilities: Vec<String>,
41}
42
43impl ProtocolNegotiation {
44 #[must_use]
46 pub const fn new(version: ProtocolVersion) -> Self {
47 Self {
48 version,
49 capabilities: Vec::new(),
50 }
51 }
52
53 #[must_use]
55 pub fn resp3_with_capabilities(capabilities: Vec<String>) -> Self {
56 Self {
57 version: ProtocolVersion::Resp3,
58 capabilities,
59 }
60 }
61
62 #[must_use]
64 pub fn has_capability(&self, capability: &str) -> bool {
65 self.capabilities.iter().any(|c| c == capability)
66 }
67}
68
69pub struct ProtocolNegotiator {
71 preferred_version: ProtocolVersion,
72}
73
74impl ProtocolNegotiator {
75 #[must_use]
77 pub const fn new(preferred_version: ProtocolVersion) -> Self {
78 Self { preferred_version }
79 }
80
81 pub async fn negotiate<T>(
91 &self,
92 connection: &mut T,
93 ) -> crate::core::error::RedisResult<ProtocolNegotiation>
94 where
95 T: ProtocolConnection,
96 {
97 match self.preferred_version {
98 ProtocolVersion::Resp2 => Ok(ProtocolNegotiation::new(ProtocolVersion::Resp2)),
99 ProtocolVersion::Resp3 => {
100 match self.try_negotiate_resp3(connection).await {
102 Ok(negotiation) => Ok(negotiation),
103 Err(_) => {
104 Ok(ProtocolNegotiation::new(ProtocolVersion::Resp2))
106 }
107 }
108 }
109 }
110 }
111
112 async fn try_negotiate_resp3<T>(
113 &self,
114 connection: &mut T,
115 ) -> crate::core::error::RedisResult<ProtocolNegotiation>
116 where
117 T: ProtocolConnection,
118 {
119 let hello_cmd = crate::core::value::RespValue::Array(vec![
121 crate::core::value::RespValue::BulkString(bytes::Bytes::from("HELLO")),
122 crate::core::value::RespValue::BulkString(bytes::Bytes::from("3")),
123 ]);
124
125 connection.send_command(&hello_cmd).await?;
126 let response = connection.read_response().await?;
127
128 match response {
130 crate::core::value::RespValue::Array(items) => {
131 let mut capabilities = Vec::new();
132
133 if items.len() >= 4 {
135 for item in items.iter().skip(3) {
137 if let crate::core::value::RespValue::BulkString(cap) = item {
138 if let Ok(cap_str) = String::from_utf8(cap.to_vec()) {
139 capabilities.push(cap_str);
140 }
141 }
142 }
143 }
144
145 Ok(ProtocolNegotiation::resp3_with_capabilities(capabilities))
146 }
147 _ => Err(crate::core::error::RedisError::Protocol(
148 "Invalid HELLO response".to_string(),
149 )),
150 }
151 }
152}
153
154impl Default for ProtocolNegotiator {
155 fn default() -> Self {
156 Self::new(ProtocolVersion::Resp2)
157 }
158}
159
160#[async_trait::async_trait]
162pub trait ProtocolConnection {
163 async fn send_command(
165 &mut self,
166 command: &crate::core::value::RespValue,
167 ) -> crate::core::error::RedisResult<()>;
168
169 async fn read_response(
171 &mut self,
172 ) -> crate::core::error::RedisResult<crate::core::value::RespValue>;
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178
179 #[test]
180 fn test_protocol_version_display() {
181 assert_eq!(ProtocolVersion::Resp2.to_string(), "RESP2");
182 assert_eq!(ProtocolVersion::Resp3.to_string(), "RESP3");
183 }
184
185 #[test]
186 fn test_protocol_negotiation() {
187 let negotiation = ProtocolNegotiation::new(ProtocolVersion::Resp2);
188 assert_eq!(negotiation.version, ProtocolVersion::Resp2);
189 assert!(negotiation.capabilities.is_empty());
190
191 let negotiation = ProtocolNegotiation::resp3_with_capabilities(vec![
192 "push".to_string(),
193 "streams".to_string(),
194 ]);
195 assert_eq!(negotiation.version, ProtocolVersion::Resp3);
196 assert!(negotiation.has_capability("push"));
197 assert!(negotiation.has_capability("streams"));
198 assert!(!negotiation.has_capability("unknown"));
199 }
200
201 #[test]
202 fn test_protocol_negotiator() {
203 let negotiator = ProtocolNegotiator::new(ProtocolVersion::Resp3);
204 assert_eq!(negotiator.preferred_version, ProtocolVersion::Resp3);
205
206 let negotiator = ProtocolNegotiator::default();
207 assert_eq!(negotiator.preferred_version, ProtocolVersion::Resp2);
208 }
209}