1use crate::enums::RedisParseError;
2use crate::enums::{RedisError, RedisResult};
3pub use crate::response::{
4 RedisResponse,
5 RedisResponse::{Array, BulkString, Error, Integer, SimpleString},
6};
7use std::str::Bytes;
8
9pub fn parse_command(command: &str) -> Result<String, RedisError> {
10 let mut tokens = Vec::<String>::new();
11 let mut cur_token = String::new();
12 let mut quoted = false;
13
14 for item in command.as_bytes() {
15 let item = *item as char;
16 match item {
17 ' ' if !quoted => {
18 tokens.push(cur_token.clone());
19 cur_token.clear()
20 }
21 '\'' if !quoted => {
22 quoted = true;
23 }
24 '\'' if quoted => {
25 quoted = false;
26 tokens.push(cur_token.clone());
27 cur_token.clear();
28 }
29 _ => cur_token.push(item),
30 }
31 }
32
33 if !cur_token.is_empty() {
34 tokens.push(cur_token);
35 }
36
37 if quoted && command.contains('\'') {
38 return Err(Box::new(RedisParseError {
39 contents: "Bad request format, Mismatch quotes".to_string(),
40 }));
41 }
42
43 let mut output = String::new();
44
45 output.push_str(&format!("*{}\r\n", tokens.len()));
46
47 for token in tokens {
48 let length = token.len();
49 output.push_str(&format!("${}\r\n{}\r\n", length, token));
50 }
51
52 Ok(output)
53}
54
55#[doc(hidden)]
56pub fn parse_response(response: &str) -> RedisResult<RedisResponse> {
57 let mut bytes = response.bytes();
58
59 let first_byte = match bytes.next() {
60 Some(value) => value,
61 None => {
62 return Err(Box::new(RedisParseError {
63 contents: "Error reading response data".to_string(),
64 }))
65 }
66 };
67
68 let response = match first_byte as char {
69 '+' => parse_simple_string(&mut bytes),
70 '-' => parse_error(&mut bytes),
71 ':' => parse_integer(&mut bytes),
72 '$' => parse_bulk_string(&mut bytes),
73 '*' => parse_array(&mut bytes),
74 _ => {
75 return Err(Box::new(RedisParseError {
76 contents: format!("unexpected byte {}, in response", first_byte),
77 }))
78 }
79 }?;
80
81 Ok(response)
82}
83
84#[doc(hidden)]
85fn parse_error(bytes: &mut std::str::Bytes) -> RedisResult<RedisResponse> {
86 let error_string = read_to_carriage_return(bytes);
87
88 Ok(Error(error_string))
89}
90
91#[doc(hidden)]
92fn parse_integer(bytes: &mut Bytes) -> RedisResult<RedisResponse> {
93 let integer_value = read_to_carriage_return(bytes);
94
95 let parsed_integer: i32 = match integer_value.parse() {
96 Ok(value) => value,
97 Err(_) => {
98 return Err(Box::new(RedisParseError {
99 contents: format!("Error parsing {} as integer", integer_value),
100 }))
101 }
102 };
103
104 Ok(Integer(parsed_integer))
105}
106
107#[doc(hidden)]
108fn parse_bulk_string(bytes: &mut Bytes) -> RedisResult<RedisResponse> {
109 let integer_value = read_to_carriage_return(bytes);
110 let mut string = String::new();
111
112 let mut num_bytes: i32 = match integer_value.parse() {
113 Ok(value) => value,
114 Err(_) => {
115 return Err(Box::new(RedisParseError {
116 contents: format!("Error parsing {} to integer", integer_value.clone()),
117 }))
118 }
119 };
120
121 for byte in bytes.skip(1) {
122 if num_bytes == 0 {
123 break;
124 }
125
126 string.push(byte as char);
127
128 num_bytes -= 1;
129 }
130
131 Ok(BulkString(string))
132}
133
134#[doc(hidden)]
135fn parse_array(_bytes: &mut Bytes) -> RedisResult<RedisResponse> {
136 todo!()
137}
138
139#[doc(hidden)]
140fn parse_simple_string(bytes: &mut Bytes) -> RedisResult<RedisResponse> {
141 let string = read_to_carriage_return(bytes);
142 Ok(SimpleString(string))
143}
144
145#[doc(hidden)]
146fn read_to_carriage_return(bytes: &mut Bytes) -> String {
147 let mut string = String::new();
148
149 for c in bytes {
150 let c = c as char;
151
152 if let '\r' = c {
153 break;
154 }
155
156 string.push(c);
157 }
158
159 string
160}
161
162#[cfg(test)]
163mod test {
164 use std::net::TcpStream;
165
166 use crate::{connection::Connection, enums::RedisError};
167 use parse::{parse_response, RedisResponse};
168
169 fn create_connection(addr: &str) -> Result<TcpStream, RedisError> {
170 Ok(TcpStream::connect(addr)?)
171 }
172
173 const HOST: &'static str = "127.0.0.1";
174 const PORT: u16 = 6379;
175 const ADDR: &'static str = "127.0.0.1:6379";
176
177 use crate::parse;
178
179 #[test]
180 fn test_parse_command() {
181 let parsed_command = parse::parse_command("GET FOO").unwrap();
182
183 assert_eq!("*2\r\n$3\r\nGET\r\n$3\r\nFOO\r\n", parsed_command);
184 }
185
186 #[test]
187 fn test_parse_quotes_handled_properly() {
188 let stream = create_connection(ADDR).unwrap();
189 let mut client = Connection::new(HOST, PORT, stream);
190
191 let _ = client.send_raw_request("set myvalue 'a custom value'");
192
193 let key = "myvalue";
194 let response = client.get(key).unwrap();
195
196 assert_eq!(
197 response,
198 RedisResponse::BulkString(String::from("a custom value"))
199 );
200 }
201
202 #[test]
203 fn test_parse_response() {
204 let data = String::from("+OK\r\n");
205
206 let response = parse_response(&data).unwrap();
207
208 assert_eq!(response, RedisResponse::SimpleString(String::from("OK")));
209 }
210
211 #[test]
212 fn test_error_response() {
213 let data = String::from("-ERROR\r\n");
214
215 let response = parse_response(&data).unwrap();
216 assert_eq!(response, RedisResponse::Error(String::from("ERROR")));
217 }
218}