redis_rs/
parse.rs

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}