From 78944338823b0a91d71de2c6a0e571ef00b6b01a Mon Sep 17 00:00:00 2001 From: han chiang Date: Sun, 1 May 2022 16:08:16 +0800 Subject: [PATCH 1/3] Create parser module. Add function and tests to parse input until crlf. --- README.md | 5 ++- src/lib.rs | 1 + src/parser/mod.rs | 1 + src/parser/parser.rs | 101 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 src/parser/mod.rs create mode 100644 src/parser/parser.rs diff --git a/README.md b/README.md index 06cee94..dff2fdf 100644 --- a/README.md +++ b/README.md @@ -125,5 +125,6 @@ $-1 # TODO -* Better separation of concern: Move parse and respond out of ClientInput -* Improve the parsing of user input. Currently, the program only parses an of array of bulk strings, and ignores the bytes part(i.e. $4 in $4\r\nping\r\n) in the bulk string request \ No newline at end of file +- [ ] Better separation of concern: Move parse and respond out of ClientInput +- [ ] Improve the parsing of user input. Currently, the program only parses an of array of bulk strings, and ignores the bytes part(i.e. $4 in $4\r\nping\r\n) in the bulk string request +- [ ] Replace threads with event loop \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 70a5cef..f94b992 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ use std::net::TcpStream; pub mod request_response; pub mod store; +pub mod parser; use crate::request_response::client_input::HandleClientInput; use request_response::client_input::ClientInput; diff --git a/src/parser/mod.rs b/src/parser/mod.rs new file mode 100644 index 0000000..b2819a7 --- /dev/null +++ b/src/parser/mod.rs @@ -0,0 +1 @@ +pub mod parser; \ No newline at end of file diff --git a/src/parser/parser.rs b/src/parser/parser.rs new file mode 100644 index 0000000..26967e3 --- /dev/null +++ b/src/parser/parser.rs @@ -0,0 +1,101 @@ +use std::io::ErrorKind; + +// https://redis.io/docs/reference/protocol-spec/ + +/* + Input: + Simple string: +ok\r\n + Error: -error message\r\n + Bulk string: $5\r\nhello\r\n, $0\r\n\r\n(empty), $-1\r\n(null) + Integer: :1000\r\n + Array: *2\r\n$3\r\nhey\r\n$5\r\nthere\r\r(2 strings), *3\r\n:1\r\n:2\r\n:3\r\n(3 integers), *0\r\n(empty), *-1\r\n(null) + - Nested array: *2\r\n*3\r\n:1\r\n:2\r\n:3\r\n*2\r\n+Hello\r\n-World\r\n +*/ + +/* + Algorithm: + - Read the first character to determine the input RESP type(simple string, error, integer, bulk string, array) + - simple string: read input until CRLF, return RESP + - error: read input until CRLF, return RESP + - bulk string: + - read input until CRLF to get number of bytes in the bulk string + - read input until CRLF to read the string + - if number of bytes match the input string, return RESP. Otherwise, return custom error + - integer: read input until CRLF, return RESP + - array: + - read input until CRLF to get number of elements in the array + - for 1..n, recursively call parse() to parse each element in the array + + Utility: + - Read input until CRLF: return a tuple of (output RESP, remaining input after CRLF) + - Check whether we have reached the end of the input + - Custom errors: unrecognised first character, CRLF not found, incomplete input(bulk string, array) +*/ + +pub enum RESPOutput { + SimpleString(String), + Error(String), + BulkString(String), + Integer(i64), + Array(Vec), +} + +pub enum Error { + UnrecognisedSymbol, + CRLFNotFound, + IncompleteInput, +} + +pub type Result<'a> = std::result::Result<(RESPOutput, &'a [u8]), Error>; + +const CR: u8 = b'\r'; +const LF: u8 = b'\n'; + +pub struct Parser {} + +impl Parser { + pub fn parse_resp(input: &[u8]) {} + + pub fn parse_until_crlf(input: &[u8]) -> std::result::Result<(&[u8], &[u8]), Error> { + for i in 0..input.len() - 1 { + if input[i] == CR && input[i + 1] == LF { + return Ok((&input[0..i], &input[i + 2..])) + } + } + Err(Error::CRLFNotFound) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_until_crlf_success() { + let input = vec!["hello world\r\n".as_bytes(), "5\r\nhello\r\n".as_bytes()]; + let expected = vec![("hello world".as_bytes(), "".as_bytes()), ("5".as_bytes(), "hello\r\n".as_bytes())]; + + for (index, inp) in input.iter().enumerate() { + let result = Parser::parse_until_crlf(*inp); + + match result { + Ok(res) => { + assert_eq!(res, expected[index]); + }, + Err(e) => panic!(e) + } + } + } + + #[test] + fn parse_until_crlf_error() { + let input = "hello world".as_bytes(); + let expected = Error::CRLFNotFound; + + let result = Parser::parse_until_crlf(input); + match result { + Ok(res) => panic!(), + Err(e) => assert!(matches!(e, expected)) + } + } +} \ No newline at end of file From 22ae236a29f633b9d55c1d76b85677098da45bec Mon Sep 17 00:00:00 2001 From: han chiang Date: Sun, 1 May 2022 22:33:22 +0800 Subject: [PATCH 2/3] Add parsing for simple string, error, integer, bulk string, array, and tests. --- src/parser/parser.rs | 501 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 486 insertions(+), 15 deletions(-) diff --git a/src/parser/parser.rs b/src/parser/parser.rs index 26967e3..c9c7f92 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -24,7 +24,7 @@ use std::io::ErrorKind; - integer: read input until CRLF, return RESP - array: - read input until CRLF to get number of elements in the array - - for 1..n, recursively call parse() to parse each element in the array + - for 1..n, recursively call parse_resp() to parse each element in the array Utility: - Read input until CRLF: return a tuple of (output RESP, remaining input after CRLF) @@ -32,21 +32,26 @@ use std::io::ErrorKind; - Custom errors: unrecognised first character, CRLF not found, incomplete input(bulk string, array) */ +#[derive(Debug, PartialEq)] pub enum RESPOutput { SimpleString(String), Error(String), BulkString(String), Integer(i64), Array(Vec), + Null, } -pub enum Error { +#[derive(Debug, PartialEq)] +pub enum ParseError { UnrecognisedSymbol, CRLFNotFound, IncompleteInput, + InvalidInput, } -pub type Result<'a> = std::result::Result<(RESPOutput, &'a [u8]), Error>; +pub type ParseResult<'a> = std::result::Result<(RESPOutput, &'a [u8]), ParseError>; +pub type ParseCRLFResult<'a> = std::result::Result<(&'a [u8], &'a [u8]), ParseError>; const CR: u8 = b'\r'; const LF: u8 = b'\n'; @@ -54,15 +59,124 @@ const LF: u8 = b'\n'; pub struct Parser {} impl Parser { - pub fn parse_resp(input: &[u8]) {} + pub fn parse_resp(input: &[u8]) -> ParseResult { + if input.len() == 0 { + return Err(ParseError::IncompleteInput); + } + let symbol_temp = String::from_utf8_lossy(&input[0..1]); + let symbol = symbol_temp.as_ref(); + let remaining = &input[1..]; + match symbol { + "+" => Parser::parse_simple_string(remaining), + "-" => Parser::parse_error(remaining), + "$" => Parser::parse_bulk_string(remaining), + ":" => Parser::parse_integer(remaining), + "*" => Parser::parse_array(remaining), + _ => return Err(ParseError::UnrecognisedSymbol), + } + } + + fn parse_simple_string(input: &[u8]) -> ParseResult { + Parser::parse_until_crlf(input).map(|(result, remaining)| { + let string = String::from(String::from_utf8_lossy(result)); + (RESPOutput::SimpleString(string), remaining) + }) + } + + fn parse_error(input: &[u8]) -> ParseResult { + Parser::parse_until_crlf(input).map(|(result, remaining)| { + let string = String::from(String::from_utf8_lossy(result)); + (RESPOutput::Error(string), remaining) + }) + } + + fn parse_bulk_string(input: &[u8]) -> ParseResult { + // First parse is to get the number of bytes in the bulk string + let parsed = Parser::parse_until_crlf(input); + if parsed.is_err() { + return Err(parsed.unwrap_err()); + } + + let (numBytes, remaining) = parsed.unwrap(); + if String::from_utf8_lossy(numBytes) == "-1" { + return Ok((RESPOutput::Null, "".as_bytes())); + } + + // Second parse is to get the string itself + let parsed = Parser::parse_until_crlf(remaining); + if parsed.is_err() { + return Err(parsed.unwrap_err()); + } + + let (result, remaining) = parsed.unwrap(); + + let numBytesInt: usize = String::from_utf8_lossy(numBytes).parse().unwrap(); + if result.len().lt(&numBytesInt) { + return Err(ParseError::IncompleteInput); + } + if result.len().gt(&numBytesInt) { + return Err(ParseError::InvalidInput); + } + + let res = String::from(String::from_utf8_lossy(result)); + Ok((RESPOutput::BulkString(res), remaining)) + } + + fn parse_integer(input: &[u8]) -> ParseResult { + let parsed = Parser::parse_until_crlf(input); + if parsed.is_err() { + return Err(parsed.unwrap_err()); + } - pub fn parse_until_crlf(input: &[u8]) -> std::result::Result<(&[u8], &[u8]), Error> { + let (result, remaining) = parsed.unwrap(); + let string = String::from(String::from_utf8_lossy(result)); + let num: i64 = match string.parse() { + Ok(res) => res, + Err(e) => return Err(ParseError::InvalidInput), + }; + Ok((RESPOutput::Integer(num), remaining)) + } + + fn parse_array(input: &[u8]) -> ParseResult { + // First parse is to get the number of elements in the array + let parsed = Parser::parse_until_crlf(input); + if parsed.is_err() { + return Err(parsed.unwrap_err()); + } + + let (numElements, remaining) = parsed.unwrap(); + if String::from_utf8_lossy(numElements) == "-1" { + return Ok((RESPOutput::Null, "".as_bytes())); + } + + let numElementsInt: u32 = match String::from(String::from_utf8_lossy(numElements)).parse() { + Ok(res) => res, + Err(e) => return Err(ParseError::InvalidInput), + }; + + let mut resp_result: Vec = vec![]; + let mut remaining = remaining; + + // Recursively parse for each element in the array + for i in 0..numElementsInt { + let parsed = Parser::parse_resp(remaining); + if parsed.is_err() { + return Err(parsed.unwrap_err()); + } + let (result, rem) = parsed.unwrap(); + resp_result.push(result); + remaining = rem; + } + return Ok((RESPOutput::Array(resp_result), remaining)); + } + + fn parse_until_crlf(input: &[u8]) -> ParseCRLFResult { for i in 0..input.len() - 1 { if input[i] == CR && input[i + 1] == LF { - return Ok((&input[0..i], &input[i + 2..])) + return Ok((&input[0..i], &input[i + 2..])); } } - Err(Error::CRLFNotFound) + Err(ParseError::CRLFNotFound) } } @@ -73,29 +187,386 @@ mod tests { #[test] fn parse_until_crlf_success() { let input = vec!["hello world\r\n".as_bytes(), "5\r\nhello\r\n".as_bytes()]; - let expected = vec![("hello world".as_bytes(), "".as_bytes()), ("5".as_bytes(), "hello\r\n".as_bytes())]; + let expected = vec![ + ("hello world".as_bytes(), "".as_bytes()), + ("5".as_bytes(), "hello\r\n".as_bytes()), + ]; for (index, inp) in input.iter().enumerate() { let result = Parser::parse_until_crlf(*inp); match result { - Ok(res) => { + Ok(res) => { assert_eq!(res, expected[index]); - }, - Err(e) => panic!(e) + } + Err(e) => panic!(e), } } } #[test] fn parse_until_crlf_error() { - let input = "hello world".as_bytes(); - let expected = Error::CRLFNotFound; + let input = "hello world".as_bytes(); + let expected = ParseError::CRLFNotFound; let result = Parser::parse_until_crlf(input); match result { Ok(res) => panic!(), - Err(e) => assert!(matches!(e, expected)) + Err(e) => assert_eq!(e, expected), + } + } + + #[test] + fn parse_simple_string_success() { + let input = "hello world\r\n".as_bytes(); + let expected = ( + RESPOutput::SimpleString(String::from("hello world")), + "".as_bytes(), + ); + + let result = Parser::parse_simple_string(input); + match result { + Ok(res) => assert_eq!(res, expected), + Err(e) => panic!(e), + } + } + + #[test] + fn parse_error_success() { + let input = "error\r\n".as_bytes(); + let expected = (RESPOutput::Error(String::from("error")), "".as_bytes()); + + let result = Parser::parse_error(input); + match result { + Ok(res) => assert_eq!(res, expected), + Err(e) => panic!(e), + } + } + + #[test] + fn parse_bulk_string_success() { + let input = vec![ + "11\r\nhello world\r\n".as_bytes(), + "0\r\n\r\n".as_bytes(), + "-1\r\n".as_bytes(), + ]; + + let expected = vec![ + ( + (RESPOutput::BulkString(String::from("hello world"))), + "".as_bytes(), + ), + ((RESPOutput::BulkString(String::from("")), "".as_bytes())), + ((RESPOutput::Null, "".as_bytes())), + ]; + + for (index, inp) in input.iter().enumerate() { + let result = Parser::parse_bulk_string(inp); + match result { + Ok(res) => assert_eq!(res, expected[index]), + Err(e) => panic!(e), + } + } + } + + #[test] + fn parse_bulk_string_error() { + let input = vec!["11\r\nhello\r\n".as_bytes(), "3\r\nhello\r\n".as_bytes()]; + let expected = vec![ParseError::IncompleteInput, ParseError::InvalidInput]; + + for (index, inp) in input.iter().enumerate() { + let result = Parser::parse_bulk_string(inp); + match result { + Ok(res) => panic!(), + Err(e) => assert_eq!(e, expected[index]), + } + } + } + + #[test] + fn parse_integer_success() { + let input = "1234\r\n".as_bytes(); + let expected = (RESPOutput::Integer(1234), "".as_bytes()); + + let result = Parser::parse_integer(input); + match result { + Ok(res) => assert_eq!(res, expected), + Err(e) => panic!(e), + } + } + + #[test] + fn parse_integer_error() { + let input = "1a\r\n".as_bytes(); + let expected = ParseError::InvalidInput; + + let result = Parser::parse_integer(input); + match result { + Ok(res) => panic!(), + Err(e) => assert_eq!(e, expected), + } + } + + #[test] + fn parse_array_success() { + let input = vec![ + "2\r\n$5\r\nhello\r\n$5\r\nworld\r\n".as_bytes(), + "3\r\n:1000\r\n+hello world\r\n-got error\r\n".as_bytes(), + "0\r\n".as_bytes(), + "-1\r\n".as_bytes(), + "2\r\n*3\r\n:1\r\n:2\r\n:3\r\n*2\r\n$5\r\nhello\r\n$5\r\nworld\r\n".as_bytes(), + ]; + let expected = vec![ + ( + RESPOutput::Array(vec![ + RESPOutput::BulkString(String::from("hello")), + RESPOutput::BulkString(String::from("world")), + ]), + "".as_bytes(), + ), + ( + RESPOutput::Array(vec![ + RESPOutput::Integer(1000), + RESPOutput::SimpleString(String::from("hello world")), + RESPOutput::Error(String::from("got error")), + ]), + "".as_bytes(), + ), + (RESPOutput::Array(vec![]), "".as_bytes()), + (RESPOutput::Null, "".as_bytes()), + ( + RESPOutput::Array(vec![ + RESPOutput::Array(vec![ + RESPOutput::Integer(1), + RESPOutput::Integer(2), + RESPOutput::Integer(3), + ]), + RESPOutput::Array(vec![ + RESPOutput::BulkString(String::from("hello")), + RESPOutput::BulkString(String::from("world")), + ]), + ]), + "".as_bytes(), + ), + ]; + + for (index, inp) in input.iter().enumerate() { + let result = Parser::parse_array(*inp); + match result { + Ok(res) => assert_eq!(res, expected[index]), + Err(e) => panic!(e), + } + } + } + + #[test] + fn parse_array_error() { + let input = vec![ + "2\r\n$5\r\nhello\r\n".as_bytes(), + "3\r\n:1000\r\n+hello world\r\n$5\r\nhello world\r\n".as_bytes(), + "2\r\n*3\r\n:1\r\n:2\r\n:3\r\n*2\r\n$5\r\nhello\r\n$5\r\nworld".as_bytes(), + ]; + let expected = vec![ + ParseError::IncompleteInput, + ParseError::InvalidInput, + ParseError::CRLFNotFound, + ]; + + for (index, inp) in input.iter().enumerate() { + let result = Parser::parse_array(*inp); + match result { + Ok(res) => panic!(), + Err(e) => assert_eq!(e, expected[index]), + } + } + } + + #[test] + fn parse_resp_incomplete_input() { + let input = "*2\r\n:1\r\n".as_bytes(); + let expected = ParseError::IncompleteInput; + + let result = Parser::parse_resp(input); + match result { + Ok(res) => panic!(), + Err(e) => assert_eq!(e, expected), + } + } + + #[test] + fn parse_resp_unrecognised_input() { + let input = "5\r\nhello\r\n".as_bytes(); + let expected = ParseError::UnrecognisedSymbol; + + let result = Parser::parse_resp(input); + match result { + Ok(res) => panic!(), + Err(e) => assert_eq!(e, expected), + } + } + + #[test] + fn parse_resp_simple_string() { + let input = "+hello world\r\n".as_bytes(); + let expected = ( + RESPOutput::SimpleString(String::from("hello world")), + "".as_bytes(), + ); + + let result = Parser::parse_resp(input); + match result { + Ok(res) => assert_eq!(res, expected), + Err(e) => panic!(e), + } + } + + #[test] + fn parse_resp_error() { + let input = "-error\r\n".as_bytes(); + let expected = (RESPOutput::Error(String::from("error")), "".as_bytes()); + + let result = Parser::parse_resp(input); + match result { + Ok(res) => assert_eq!(res, expected), + Err(e) => panic!(e), + } + } + + #[test] + fn parse_resp_bulk_string_success() { + let input = vec![ + "$11\r\nhello world\r\n".as_bytes(), + "$0\r\n\r\n".as_bytes(), + "$-1\r\n".as_bytes(), + ]; + + let expected = vec![ + ( + (RESPOutput::BulkString(String::from("hello world"))), + "".as_bytes(), + ), + ((RESPOutput::BulkString(String::from("")), "".as_bytes())), + ((RESPOutput::Null, "".as_bytes())), + ]; + + for (index, inp) in input.iter().enumerate() { + let result = Parser::parse_resp(inp); + match result { + Ok(res) => assert_eq!(res, expected[index]), + Err(e) => panic!(e), + } + } + } + + #[test] + fn parse_resp_bulk_string_error() { + let input = vec!["$11\r\nhello\r\n".as_bytes(), "$3\r\nhello\r\n".as_bytes()]; + let expected = vec![ParseError::IncompleteInput, ParseError::InvalidInput]; + + for (index, inp) in input.iter().enumerate() { + let result = Parser::parse_resp(inp); + match result { + Ok(res) => panic!(), + Err(e) => assert_eq!(e, expected[index]), + } + } + } + + #[test] + fn parse_resp_integer_success() { + let input = ":1234\r\n".as_bytes(); + let expected = (RESPOutput::Integer(1234), "".as_bytes()); + + let result = Parser::parse_resp(input); + match result { + Ok(res) => assert_eq!(res, expected), + Err(e) => panic!(e), + } + } + + #[test] + fn parse_resp_integer_error() { + let input = ":1a\r\n".as_bytes(); + let expected = ParseError::InvalidInput; + + let result = Parser::parse_resp(input); + match result { + Ok(res) => panic!(), + Err(e) => assert_eq!(e, expected), + } + } + + #[test] + fn parse_resp_array_success() { + let input = vec![ + "*2\r\n$5\r\nhello\r\n$5\r\nworld\r\n".as_bytes(), + "*3\r\n:1000\r\n+hello world\r\n-got error\r\n".as_bytes(), + "*0\r\n".as_bytes(), + "*-1\r\n".as_bytes(), + "*2\r\n*3\r\n:1\r\n:2\r\n:3\r\n*2\r\n$5\r\nhello\r\n$5\r\nworld\r\n".as_bytes(), + ]; + let expected = vec![ + ( + RESPOutput::Array(vec![ + RESPOutput::BulkString(String::from("hello")), + RESPOutput::BulkString(String::from("world")), + ]), + "".as_bytes(), + ), + ( + RESPOutput::Array(vec![ + RESPOutput::Integer(1000), + RESPOutput::SimpleString(String::from("hello world")), + RESPOutput::Error(String::from("got error")), + ]), + "".as_bytes(), + ), + (RESPOutput::Array(vec![]), "".as_bytes()), + (RESPOutput::Null, "".as_bytes()), + ( + RESPOutput::Array(vec![ + RESPOutput::Array(vec![ + RESPOutput::Integer(1), + RESPOutput::Integer(2), + RESPOutput::Integer(3), + ]), + RESPOutput::Array(vec![ + RESPOutput::BulkString(String::from("hello")), + RESPOutput::BulkString(String::from("world")), + ]), + ]), + "".as_bytes(), + ), + ]; + + for (index, inp) in input.iter().enumerate() { + let result = Parser::parse_resp(*inp); + match result { + Ok(res) => assert_eq!(res, expected[index]), + Err(e) => panic!(e), + } + } + } + + #[test] + fn parse_resp_array_error() { + let input = vec![ + "*2\r\n$5\r\nhello\r\n".as_bytes(), + "*3\r\n:1000\r\n+hello world\r\n$5\r\nhello world\r\n".as_bytes(), + "*2\r\n*3\r\n:1\r\n:2\r\n:3\r\n*2\r\n$5\r\nhello\r\n$5\r\nworld".as_bytes(), + ]; + let expected = vec![ + ParseError::IncompleteInput, + ParseError::InvalidInput, + ParseError::CRLFNotFound, + ]; + + for (index, inp) in input.iter().enumerate() { + let result = Parser::parse_resp(*inp); + match result { + Ok(res) => panic!(), + Err(e) => assert_eq!(e, expected[index]), + } } } -} \ No newline at end of file +} From 4ec02e4ba8b370eb2a03aa15ccb36947a538fff7 Mon Sep 17 00:00:00 2001 From: han chiang Date: Sun, 1 May 2022 22:35:20 +0800 Subject: [PATCH 3/3] Fix warnings --- src/parser/parser.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/parser/parser.rs b/src/parser/parser.rs index c9c7f92..fffa807 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -97,8 +97,8 @@ impl Parser { return Err(parsed.unwrap_err()); } - let (numBytes, remaining) = parsed.unwrap(); - if String::from_utf8_lossy(numBytes) == "-1" { + let (num_bytes, remaining) = parsed.unwrap(); + if String::from_utf8_lossy(num_bytes) == "-1" { return Ok((RESPOutput::Null, "".as_bytes())); } @@ -110,11 +110,11 @@ impl Parser { let (result, remaining) = parsed.unwrap(); - let numBytesInt: usize = String::from_utf8_lossy(numBytes).parse().unwrap(); - if result.len().lt(&numBytesInt) { + let num_bytes_int: usize = String::from_utf8_lossy(num_bytes).parse().unwrap(); + if result.len().lt(&num_bytes_int) { return Err(ParseError::IncompleteInput); } - if result.len().gt(&numBytesInt) { + if result.len().gt(&num_bytes_int) { return Err(ParseError::InvalidInput); } @@ -132,7 +132,7 @@ impl Parser { let string = String::from(String::from_utf8_lossy(result)); let num: i64 = match string.parse() { Ok(res) => res, - Err(e) => return Err(ParseError::InvalidInput), + Err(_) => return Err(ParseError::InvalidInput), }; Ok((RESPOutput::Integer(num), remaining)) } @@ -144,21 +144,21 @@ impl Parser { return Err(parsed.unwrap_err()); } - let (numElements, remaining) = parsed.unwrap(); - if String::from_utf8_lossy(numElements) == "-1" { + let (num_elements, remaining) = parsed.unwrap(); + if String::from_utf8_lossy(num_elements) == "-1" { return Ok((RESPOutput::Null, "".as_bytes())); } - let numElementsInt: u32 = match String::from(String::from_utf8_lossy(numElements)).parse() { + let num_elements_int: u32 = match String::from(String::from_utf8_lossy(num_elements)).parse() { Ok(res) => res, - Err(e) => return Err(ParseError::InvalidInput), + Err(_) => return Err(ParseError::InvalidInput), }; let mut resp_result: Vec = vec![]; let mut remaining = remaining; // Recursively parse for each element in the array - for i in 0..numElementsInt { + for _ in 0..num_elements_int { let parsed = Parser::parse_resp(remaining); if parsed.is_err() { return Err(parsed.unwrap_err());