1#[derive(Debug, Clone, thiserror::Error)]
26pub enum ParseError {
27 #[error("incomplete")]
29 Incomplete,
30 #[error("invalid response")]
32 Invalid,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum Request {
38 Ping,
40}
41
42impl Request {
43 pub fn encode(self, buf: &mut [u8]) -> usize {
47 const PING_CMD: &[u8] = b"PING\r\n";
48 let len = PING_CMD.len();
49 buf[..len].copy_from_slice(PING_CMD);
50 len
51 }
52
53 pub const fn encoded_len(self) -> usize {
55 6 }
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub enum Response {
62 Pong,
64 Error,
66}
67
68impl Response {
69 pub fn parse(data: &[u8]) -> Result<(Self, usize), ParseError> {
75 const PONG_SIMPLE: &[u8] = b"PONG\r\n";
76 const PONG_RESP: &[u8] = b"+PONG\r\n";
77
78 if data.is_empty() {
79 return Err(ParseError::Incomplete);
80 }
81
82 if data[0] == b'+' {
84 if data.len() < PONG_RESP.len() {
85 if PONG_RESP.starts_with(data) {
86 return Err(ParseError::Incomplete);
87 }
88 } else if data.starts_with(PONG_RESP) {
89 return Ok((Response::Pong, PONG_RESP.len()));
90 }
91
92 if let Some(pos) = data.iter().position(|&b| b == b'\n') {
94 return Ok((Response::Pong, pos + 1));
95 }
96 return Err(ParseError::Incomplete);
97 }
98
99 if data.starts_with(b"PONG") {
101 if data.len() < PONG_SIMPLE.len() {
102 if PONG_SIMPLE.starts_with(data) {
103 return Err(ParseError::Incomplete);
104 }
105 } else if data.starts_with(PONG_SIMPLE) {
106 return Ok((Response::Pong, PONG_SIMPLE.len()));
107 }
108 }
109
110 if data[0] == b'-' {
112 if let Some(pos) = data.iter().position(|&b| b == b'\n') {
113 return Ok((Response::Error, pos + 1));
114 }
115 return Err(ParseError::Incomplete);
116 }
117
118 if PONG_SIMPLE.starts_with(data) {
120 return Err(ParseError::Incomplete);
121 }
122
123 Err(ParseError::Invalid)
125 }
126
127 pub fn encode(self, buf: &mut [u8]) -> usize {
131 let data = match self {
132 Response::Pong => b"PONG\r\n",
133 Response::Error => b"-ERR\r\n",
134 };
135 buf[..data.len()].copy_from_slice(data);
136 data.len()
137 }
138
139 pub fn is_error(self) -> bool {
141 matches!(self, Response::Error)
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn test_encode_ping() {
151 let mut buf = [0u8; 16];
152 let len = Request::Ping.encode(&mut buf);
153 assert_eq!(&buf[..len], b"PING\r\n");
154 assert_eq!(len, 6);
155 }
156
157 #[test]
158 fn test_parse_pong_simple() {
159 let (resp, consumed) = Response::parse(b"PONG\r\n").unwrap();
160 assert_eq!(resp, Response::Pong);
161 assert_eq!(consumed, 6);
162 }
163
164 #[test]
165 fn test_parse_pong_resp() {
166 let (resp, consumed) = Response::parse(b"+PONG\r\n").unwrap();
167 assert_eq!(resp, Response::Pong);
168 assert_eq!(consumed, 7);
169 }
170
171 #[test]
172 fn test_parse_pong_resp_other() {
173 let (resp, consumed) = Response::parse(b"+OK\r\n").unwrap();
175 assert_eq!(resp, Response::Pong);
176 assert_eq!(consumed, 5);
177 }
178
179 #[test]
180 fn test_parse_error() {
181 let (resp, consumed) = Response::parse(b"-ERR unknown command\r\n").unwrap();
182 assert_eq!(resp, Response::Error);
183 assert_eq!(consumed, 22);
184 }
185
186 #[test]
187 fn test_parse_incomplete() {
188 assert!(matches!(Response::parse(b""), Err(ParseError::Incomplete)));
189 assert!(matches!(
190 Response::parse(b"PO"),
191 Err(ParseError::Incomplete)
192 ));
193 assert!(matches!(
194 Response::parse(b"PONG"),
195 Err(ParseError::Incomplete)
196 ));
197 assert!(matches!(
198 Response::parse(b"PONG\r"),
199 Err(ParseError::Incomplete)
200 ));
201 assert!(matches!(
202 Response::parse(b"+PON"),
203 Err(ParseError::Incomplete)
204 ));
205 }
206
207 #[test]
208 fn test_parse_invalid() {
209 assert!(matches!(
210 Response::parse(b"INVALID\r\n"),
211 Err(ParseError::Invalid)
212 ));
213 assert!(matches!(
214 Response::parse(b"XONG\r\n"),
215 Err(ParseError::Invalid)
216 ));
217 }
218
219 #[test]
220 fn test_encode_response() {
221 let mut buf = [0u8; 16];
222
223 let len = Response::Pong.encode(&mut buf);
224 assert_eq!(&buf[..len], b"PONG\r\n");
225
226 let len = Response::Error.encode(&mut buf);
227 assert_eq!(&buf[..len], b"-ERR\r\n");
228 }
229
230 #[test]
231 fn test_roundtrip() {
232 let mut buf = [0u8; 16];
233 let len = Response::Pong.encode(&mut buf);
234 let (resp, consumed) = Response::parse(&buf[..len]).unwrap();
235 assert_eq!(resp, Response::Pong);
236 assert_eq!(consumed, len);
237 }
238}