diff options
Diffstat (limited to 'src/resp_parser.rs')
| -rw-r--r-- | src/resp_parser.rs | 1123 |
1 files changed, 138 insertions, 985 deletions
diff --git a/src/resp_parser.rs b/src/resp_parser.rs index 0313679..0563188 100644 --- a/src/resp_parser.rs +++ b/src/resp_parser.rs @@ -194,7 +194,7 @@ impl RespError { } } -fn parse_simple_strings(bytes: &[u8]) -> Result<(RespType, &[u8]), RespError> { +pub fn parse_simple_strings(bytes: &[u8]) -> Result<(RespType, &[u8]), RespError> { match bytes { [first, rest @ ..] => { if *first != SIMPLE_STRING { @@ -218,7 +218,7 @@ fn parse_simple_strings(bytes: &[u8]) -> Result<(RespType, &[u8]), RespError> { } } -fn parse_simple_errors(bytes: &[u8]) -> Result<(RespType, &[u8]), RespError> { +pub fn parse_simple_errors(bytes: &[u8]) -> Result<(RespType, &[u8]), RespError> { match bytes { [first, rest @ ..] => { if *first != SIMPLE_ERROR { @@ -242,7 +242,7 @@ fn parse_simple_errors(bytes: &[u8]) -> Result<(RespType, &[u8]), RespError> { } } -fn parse_integers(bytes: &[u8]) -> Result<(RespType, &[u8]), RespError> { +pub fn parse_integers(bytes: &[u8]) -> Result<(RespType, &[u8]), RespError> { match bytes { [first, rest @ ..] => { if *first != INTEGER { @@ -299,12 +299,16 @@ pub fn parse(bytes: &[u8]) -> Result<(RespType, &[u8]), RespError> { let (parsed, remain) = parse_nulls(bytes)?; Ok((parsed, remain)) } + MAPS => { + let (parsed, remain) = parse_maps(bytes)?; + Ok((parsed, remain)) + } _ => Err(RespError::InvalidDataType), } } -fn parse_array(bytes: &[u8]) -> Result<(RespType, &[u8]), RespError> { +pub fn parse_array(bytes: &[u8]) -> Result<(RespType, &[u8]), RespError> { match bytes { [first, rest @ ..] => { if *first != ARRAY { @@ -343,7 +347,7 @@ fn parse_array(bytes: &[u8]) -> Result<(RespType, &[u8]), RespError> { } } -fn parse_bulk_strings(bytes: &[u8]) -> Result<(RespType, &[u8]), RespError> { +pub fn parse_bulk_strings(bytes: &[u8]) -> Result<(RespType, &[u8]), RespError> { match bytes { [first, rest @ ..] => { if *first != BULK_STRING { @@ -389,7 +393,7 @@ fn parse_bulk_strings(bytes: &[u8]) -> Result<(RespType, &[u8]), RespError> { } } -fn parse_nulls(bytes: &[u8]) -> Result<(RespType, &[u8]), RespError> { +pub fn parse_nulls(bytes: &[u8]) -> Result<(RespType, &[u8]), RespError> { match bytes { [first, rest @ ..] => { if *first != NULL { @@ -413,7 +417,7 @@ fn parse_nulls(bytes: &[u8]) -> Result<(RespType, &[u8]), RespError> { } } -fn parse_boolean(bytes: &[u8]) -> Result<(RespType, &[u8]), RespError> { +pub fn parse_boolean(bytes: &[u8]) -> Result<(RespType, &[u8]), RespError> { match bytes { [first, rest @ ..] => { if *first != BOOLEAN { @@ -444,7 +448,7 @@ fn parse_boolean(bytes: &[u8]) -> Result<(RespType, &[u8]), RespError> { } } -fn parse_doubles(bytes: &[u8]) -> Result<(RespType, &[u8]), RespError> { +pub fn parse_doubles(bytes: &[u8]) -> Result<(RespType, &[u8]), RespError> { match bytes { [first, rest @ ..] => { if *first != DOUBLES { @@ -467,51 +471,105 @@ fn parse_doubles(bytes: &[u8]) -> Result<(RespType, &[u8]), RespError> { } } -fn parse_big_numbers() { - todo!() +pub fn parse_maps(bytes: &[u8]) -> Result<(RespType, &[u8]), RespError> { + match bytes { + [first, rest @ ..] => { + if *first != MAPS { + return Err(RespError::InvalidDataType); + } + + // this would consume the <digit>\r\n + let (consumed, mut remained) = rest + .windows(2) + .position(|window| window == b"\r\n") + .map(|pos| (&rest[..pos], &rest[pos + 2..])) + .ok_or(RespError::UnexpectedEnd)?; + + // should equal the digit + let length = String::from_utf8_lossy(consumed) + .parse::<u64>() + .map_err(|_| RespError::InvalidValue)?; + + let mut map: HashMap<String, RespType> = HashMap::new(); + + let mut key_set: HashSet<String> = HashSet::new(); + + // I mean this is pretty unredable but it is what it is :/ + // The redundant !remained.is_empty() is because the parse function should handle the + // empty bytes but that would mean I refactor the parse to return and (Option<RespType>, &[u8]) + // Which is kind of a lot of work now so this works for now I should probably do this for arrray parsing I think + for _ in 0..length { + if !remained.is_empty() { + if !remained.is_empty() { + let (key, rest) = parse(remained)?; + key_set.insert(key.to_resp_string()); + dbg!(&key); + remained = rest; + if !remained.is_empty() { + let (value, rest) = parse(remained)?; + dbg!(&value); + remained = rest; + map.insert(key.to_resp_string(), value); + } + } + } + } + + // I need this because if the user sent the same key it should override and the check + // for unexpected end fails because it would expect the length of the map that was intended by length variable + if map.len() != key_set.len() { + return Err(RespError::UnexpectedEnd); + } + + let consumed = RespType::Maps(map); + + return Ok((consumed, remained)); + } + [] => Err(RespError::Custom(String::from("Empty data"))), + } } -fn parse_sets() { +pub fn parse_big_numbers() { todo!() } -fn parse_maps() { +pub fn parse_sets() { todo!() } -fn parse_verbatim_string() { +pub fn parse_verbatim_string() { todo!() } -fn parse_bulk_errors() { +pub fn parse_bulk_errors() { todo!() } -fn parse_attributes() { +pub fn parse_attributes() { todo!() } -fn parse_pushes() { +pub fn parse_pushes() { todo!() } #[derive(Debug, Clone)] pub enum RespType { - SimpleString(String), // + - SimpleError(String), // - - Integer(u64), // : - BulkString(Vec<u8>), // $ - Array(Vec<RespType>), // * - Null(), // _ - Boolean(bool), // # - Doubles(f64), // , - BigNumbers(String), // ( - BulkErrors(Vec<RespType>), // ! - VerbatimStrings(Vec<RespType>), // = - Maps(HashMap<RespType, RespType>), // % - Attributes(Vec<RespType>), // | - Sets(HashSet<RespType>), // ~ - Pushes(Vec<RespType>), // > + SimpleString(String), // + + SimpleError(String), // - + Integer(u64), // : + BulkString(Vec<u8>), // $ + Array(Vec<RespType>), // * + Null(), // _ + Boolean(bool), // # + Doubles(f64), // , + BigNumbers(String), // ( + BulkErrors(Vec<RespType>), // ! + VerbatimStrings(Vec<RespType>), // = + Maps(HashMap<String, RespType>), // % + Attributes(Vec<RespType>), // | + Sets(HashSet<String>), // ~ + Pushes(Vec<RespType>), // > } impl RespType { @@ -560,6 +618,54 @@ impl RespType { } } } + + pub fn to_resp_string(&self) -> String { + match self { + RespType::SimpleString(s) => format!("{}", s), + RespType::SimpleError(s) => format!("{}", s), + RespType::Integer(i) => format!("{}", i), + RespType::BulkString(bytes) => { + let s = String::from_utf8_lossy(bytes); + format!("{}", s) + } + RespType::Array(arr) => { + let elements = arr + .iter() + .map(|e| e.to_resp_bytes()) + .collect::<Vec<Vec<u8>>>(); + // TODO: Implement proper Display for elements because this will definitely not + // work + format!("{:?}", elements) + } + // this is just a hack because the platform uses RESP2 in RESP3 it should be "_\r\n" + RespType::Null() => "-1".to_string(), + RespType::Boolean(b) => format!("{}", if *b { "t" } else { "f" }), + RespType::Doubles(d) => format!("{}", d), + RespType::BigNumbers(n) => format!("{}", n), + RespType::Maps(map) => { + let pairs: Vec<String> = map + .iter() + .map(|(key, value)| format!("{}: {}", key, value.to_resp_string())) + .collect(); + format!("{{{}}}", pairs.join(", ")) + } + RespType::BulkErrors(errors) => { + todo!() + } + RespType::VerbatimStrings(strings) => { + todo!() + } + RespType::Attributes(attrs) => { + todo!() + } + RespType::Sets(set) => { + todo!() + } + RespType::Pushes(pushes) => { + todo!() + } + } + } } impl PartialEq for RespType { @@ -576,7 +682,7 @@ impl PartialEq for RespType { (RespType::BigNumbers(a), RespType::BigNumbers(b)) => a == b, (RespType::BulkErrors(a), RespType::BulkErrors(b)) => a == b, (RespType::VerbatimStrings(a), RespType::VerbatimStrings(b)) => a == b, - // (RespType::Maps(a), RespType::Maps(b)) => a == b, + (RespType::Maps(a), RespType::Maps(b)) => a == b, (RespType::Attributes(a), RespType::Attributes(b)) => a == b, // (RespType::Sets(a), RespType::Sets(b)) => a == b, (RespType::Pushes(a), RespType::Pushes(b)) => a == b, @@ -667,956 +773,3 @@ impl PartialEq<f64> for RespType { } } } - -// Test module -#[cfg(test)] -mod tests { - use super::*; - - mod simple_strings_tests { - use super::*; - - #[test] - fn test_valid_simple_strings() { - // Basic valid cases - assert_eq!(parse_simple_strings(b"+OK\r\n").unwrap().0, "OK"); - assert_eq!(parse_simple_strings(b"+PONG\r\n").unwrap().0, "PONG"); - assert_eq!( - parse_simple_strings(b"+Hello World\r\n").unwrap().0, - "Hello World" - ); - - // Empty string - assert_eq!(parse_simple_strings(b"+\r\n").unwrap().0, ""); - - // String with spaces and special characters (but no \r or \n) - assert_eq!( - parse_simple_strings(b"+Hello, World! 123\r\n").unwrap().0, - "Hello, World! 123" - ); - - // String with various ASCII characters - assert_eq!( - parse_simple_strings(b"+!@#$%^&*()_+-={}[]|\\:;\"'<>?,./ \r\n") - .unwrap() - .0, - "!@#$%^&*()_+-={}[]|\\:;\"'<>?,./ " - ); - - // Unicode characters (should work with UTF-8) - assert_eq!( - parse_simple_strings(b"+\xc3\xa9\xc3\xa1\xc3\xb1\r\n") - .unwrap() - .0, - "éáñ" - ); - } - - #[test] - fn test_invalid_prefix() { - // Missing '+' prefix - assert_eq!( - parse_simple_strings(b"OK\r\n").err().unwrap().message(), - "WRONGTYPE Operation against a key holding the wrong kind of value" - ); - - // Wrong prefix - assert_eq!( - parse_simple_strings(b"-Error\r\n").err().unwrap().message(), - "WRONGTYPE Operation against a key holding the wrong kind of value" - ); - assert_eq!( - parse_simple_strings(b":123\r\n").err().unwrap().message(), - "WRONGTYPE Operation against a key holding the wrong kind of value" - ); - assert_eq!( - parse_simple_strings(b"$5\r\nhello\r\n") - .err() - .unwrap() - .message(), - "WRONGTYPE Operation against a key holding the wrong kind of value" - ); - } - - #[test] - fn test_missing_crlf_terminator() { - // No CRLF at all - assert_eq!( - parse_simple_strings(b"+OK").err().unwrap().message(), - "ERR Unexpected end of input" - ); - - // Only \r - assert_eq!( - parse_simple_strings(b"+OK\r").err().unwrap().message(), - "ERR Unexpected end of input" - ); - - // Only \n - assert_eq!( - parse_simple_strings(b"+OK\n").err().unwrap().message(), - "ERR Unexpected end of input" - ); - - // Wrong order (\n\r instead of \r\n) - assert_eq!( - parse_simple_strings(b"+OK\n\r").err().unwrap().message(), - "ERR Unexpected end of input" - ); - } - - #[test] - fn test_invalid_characters_in_content() { - // Contains \r in content - assert_eq!( - parse_simple_strings(b"+Hello\rWorld\r\n") - .err() - .unwrap() - .message(), - "ERR invalid value" - ); - - // Contains \n in content - assert_eq!( - parse_simple_strings(b"+Hello\nWorld\r\n") - .err() - .unwrap() - .message(), - "ERR invalid value" - ); - } - - #[test] - fn test_empty_input() { - assert_eq!( - parse_simple_strings(b"").err().unwrap().message(), - "ERR Empty data" - ); - } - - #[test] - fn test_with_trailing_data() { - // RESP simple string with extra data after CRLF (should be ignored) - assert_eq!(parse_simple_strings(b"+OK\r\nextra_data").unwrap().0, "OK"); - assert_eq!( - parse_simple_strings(b"+PONG\r\n+another_string\r\n") - .unwrap() - .0, - "PONG" - ); - } - - #[test] - fn test_real_world_redis_responses() { - // Common Redis simple string responses - assert_eq!(parse_simple_strings(b"+OK\r\n").unwrap().0, "OK"); - assert_eq!(parse_simple_strings(b"+PONG\r\n").unwrap().0, "PONG"); - assert_eq!(parse_simple_strings(b"+QUEUED\r\n").unwrap().0, "QUEUED"); - - // Redis status responses - assert_eq!( - parse_simple_strings(b"+Background saving started\r\n") - .unwrap() - .0, - "Background saving started" - ); - assert_eq!( - parse_simple_strings(b"+Background saving successfully finished\r\n") - .unwrap() - .0, - "Background saving successfully finished" - ); - } - - #[test] - fn test_edge_cases() { - // Just the prefix and CRLF - assert_eq!(parse_simple_strings(b"+\r\n").unwrap().0, ""); - - // Long string - let long_string = "a".repeat(1000); - let mut input = b"+".to_vec(); - input.extend_from_slice(long_string.as_bytes()); - input.extend_from_slice(b"\r\n"); - assert_eq!(parse_simple_strings(&input).unwrap().0, long_string); - - // String with only spaces - assert_eq!(parse_simple_strings(b"+ \r\n").unwrap().0, " "); - - // String with tabs and other whitespace - assert_eq!(parse_simple_strings(b"+\t \t\r\n").unwrap().0, "\t \t"); - } - - #[test] - fn test_binary_safety_within_limits() { - // Non-UTF8 bytes (but no \r or \n) - let mut input = b"+".to_vec(); - input.extend_from_slice(&[0xFF, 0xFE, 0xFD]); // Invalid UTF-8 - input.extend_from_slice(b"\r\n"); - - // Should handle invalid UTF-8 gracefully with replacement characters - if let RespType::SimpleString(data) = parse_simple_strings(&input).unwrap().0 { - assert!(!data.is_empty()); // Should contain replacement characters - } - } - } - - mod simple_errors_tests { - use super::*; - - #[test] - fn test_valid_simple_errors() { - // Basic valid cases - assert_eq!( - parse_simple_errors(b"-ERR unknown command\r\n").unwrap().0, - "ERR unknown command" - ); - assert_eq!( - parse_simple_errors(b"-WRONGTYPE\r\n").unwrap().0, - "WRONGTYPE" - ); - assert_eq!( - parse_simple_errors(b"-ERR syntax error\r\n").unwrap().0, - "ERR syntax error" - ); - - // Empty error string - assert_eq!(parse_simple_errors(b"-\r\n").unwrap().0, ""); - - // Error with spaces and special characters (but no \r or \n) - assert_eq!( - parse_simple_errors(b"-ERR invalid key: 'test123'\r\n") - .unwrap() - .0, - "ERR invalid key: 'test123'" - ); - - // Error with various ASCII characters - assert_eq!( - parse_simple_errors(b"-ERR !@#$%^&*()_+-={}[]|\\:;\"'<>?,./ \r\n") - .unwrap() - .0, - "ERR !@#$%^&*()_+-={}[]|\\:;\"'<>?,./ " - ); - - // Unicode characters in error message - assert_eq!( - parse_simple_errors(b"-ERR \xc3\xa9\xc3\xa1\xc3\xb1\r\n") - .unwrap() - .0, - "ERR éáñ" - ); - - // Common Redis error patterns - assert_eq!( - parse_simple_errors(b"-NOAUTH Authentication required\r\n") - .unwrap() - .0, - "NOAUTH Authentication required" - ); - assert_eq!( - parse_simple_errors( - b"-WRONGTYPE Operation against a key holding the wrong kind of value\r\n" - ) - .unwrap() - .0, - "WRONGTYPE Operation against a key holding the wrong kind of value" - ); - } - - #[test] - fn test_invalid_simple_errors() { - // Wrong data type marker - assert_eq!( - parse_simple_errors(b"+OK\r\n").err().unwrap().message(), - "ERR Invalid data type" - ); - - // Contains \r in content - assert_eq!( - parse_simple_errors(b"-ERR invalid\r character\r\n") - .err() - .unwrap() - .message(), - "ERR invalid value" - ); - - // Contains \n in content - assert_eq!( - parse_simple_errors(b"-ERR invalid\n character\r\n") - .err() - .unwrap() - .message(), - "ERR invalid value" - ); - - // Missing \r\n terminator - assert_eq!( - parse_simple_errors(b"-ERR no terminator") - .err() - .unwrap() - .message(), - "ERR Unexpected end of input" - ); - - // Only \r without \n - assert_eq!( - parse_simple_errors(b"-ERR only carriage return\r") - .err() - .unwrap() - .message(), - "ERR Unexpected end of input" - ); - - // Only \n without \r - assert_eq!( - parse_simple_errors(b"-ERR only newline\n") - .err() - .unwrap() - .message(), - "ERR Unexpected end of input" - ); - - // Empty input - assert_eq!( - parse_simple_errors(b"").err().unwrap().message(), - "ERR Empty data" - ); - - // Just the marker without content - assert_eq!( - parse_simple_errors(b"-").err().unwrap().message(), - "ERR Unexpected end of input" - ); - } - - #[test] - fn test_simple_error_remaining_bytes() { - // Test that remaining bytes are correctly returned - let (error, remaining) = parse_simple_errors(b"-ERR test\r\nnext data").unwrap(); - assert_eq!(error, "ERR test"); - assert_eq!(remaining, b"next data"); - - // Test with multiple commands - let (error, remaining) = parse_simple_errors(b"-WRONGTYPE\r\n+OK\r\n").unwrap(); - assert_eq!(error, "WRONGTYPE"); - assert_eq!(remaining, b"+OK\r\n"); - - // Test with no remaining data - let (error, remaining) = parse_simple_errors(b"-ERR final\r\n").unwrap(); - assert_eq!(error, "ERR final"); - assert_eq!(remaining, b""); - } - } - - mod integeres_tests { - use super::*; - - #[test] - fn test_valid_integers() { - // Basic valid cases - assert_eq!(parse_integers(b":0\r\n").unwrap().0, 0u64); - assert_eq!(parse_integers(b":1\r\n").unwrap().0, 1u64); - assert_eq!(parse_integers(b":42\r\n").unwrap().0, 42u64); - assert_eq!(parse_integers(b":1000\r\n").unwrap().0, 1000u64); - - assert_eq!(parse_integers(b":+42\r\n").unwrap().0, 42u64); - - // Large numbers - assert_eq!( - parse_integers(b":9223372036854775807\r\n").unwrap().0, - 9223372036854775807u64 - ); - assert_eq!( - parse_integers(b":18446744073709551615\r\n").unwrap().0, - 18446744073709551615u64 - ); // u64::MAX - - // Edge cases - assert_eq!(parse_integers(b":123456789\r\n").unwrap().0, 123456789u64); - - // Numbers with leading zeros (should still parse correctly) - assert_eq!(parse_integers(b":0000042\r\n").unwrap().0, 42u64); - assert_eq!(parse_integers(b":00000\r\n").unwrap().0, 0u64); - } - - #[test] - fn test_invalid_integers() { - // Wrong data type marker - assert_eq!( - parse_integers(b"+42\r\n").err().unwrap().message(), - "ERR Invalid data type" - ); - - // Negative numbers (not valid for u64) - assert_eq!( - parse_integers(b":-42\r\n").err().unwrap().message(), - "ERR invalid value" - ); - - // Non-numeric content - assert_eq!( - parse_integers(b":abc\r\n").err().unwrap().message(), - "ERR invalid value" - ); - - // Mixed numeric and non-numeric - assert_eq!( - parse_integers(b":42abc\r\n").err().unwrap().message(), - "ERR invalid value" - ); - - // Empty integer - assert_eq!( - parse_integers(b":\r\n").err().unwrap().message(), - "ERR invalid value" - ); - - // Contains \r in content - assert_eq!( - parse_integers(b":42\r23\r\n").err().unwrap().message(), - "ERR invalid value" - ); - - // Contains \n in content - assert_eq!( - parse_integers(b":42\n23\r\n").err().unwrap().message(), - "ERR invalid value" - ); - - // Missing \r\n terminator - assert_eq!( - parse_integers(b":42").err().unwrap().message(), - "ERR Unexpected end of input" - ); - - // Only \r without \n - assert_eq!( - parse_integers(b":42\r").err().unwrap().message(), - "ERR Unexpected end of input" - ); - - // Only \n without \r - assert_eq!( - parse_integers(b":42\n").err().unwrap().message(), - "ERR Unexpected end of input" - ); - - // Empty input - assert_eq!( - parse_integers(b"").err().unwrap().message(), - "ERR Empty data" - ); - - // Just the marker without content - assert_eq!( - parse_integers(b":").err().unwrap().message(), - "ERR Unexpected end of input" - ); - - // Number too large for u64 - assert_eq!( - parse_integers(b":18446744073709551616\r\n") // u64::MAX + 1 - .err() - .unwrap() - .message(), - "ERR invalid value" - ); - - // Floating point numbers - assert_eq!( - parse_integers(b":42.5\r\n").err().unwrap().message(), - "ERR invalid value" - ); - - // Scientific notation - assert_eq!( - parse_integers(b":1e5\r\n").err().unwrap().message(), - "ERR invalid value" - ); - - // Hexadecimal numbers - assert_eq!( - parse_integers(b":0x42\r\n").err().unwrap().message(), - "ERR invalid value" - ); - - // Whitespace - assert_eq!( - parse_integers(b": 42\r\n").err().unwrap().message(), - "ERR invalid value" - ); - - assert_eq!( - parse_integers(b":42 \r\n").err().unwrap().message(), - "ERR invalid value" - ); - } - - #[test] - fn test_integer_remaining_bytes() { - // Test that remaining bytes are correctly returned - let (integer, remaining) = parse_integers(b":42\r\nnext data").unwrap(); - assert_eq!(integer, 42u64); - assert_eq!(remaining, b"next data"); - - // Test with multiple commands - let (integer, remaining) = parse_integers(b":1337\r\n+OK\r\n").unwrap(); - assert_eq!(integer, 1337u64); - assert_eq!(remaining, b"+OK\r\n"); - - // Test with no remaining data - let (integer, remaining) = parse_integers(b":999\r\n").unwrap(); - assert_eq!(integer, 999u64); - assert_eq!(remaining, b""); - - // Test with zero and remaining data - let (integer, remaining) = parse_integers(b":0\r\n-ERR test\r\n").unwrap(); - assert_eq!(integer, 0u64); - assert_eq!(remaining, b"-ERR test\r\n"); - } - } - - #[cfg(test)] - mod bulk_string_tests { - use super::*; - - #[test] - fn test_valid_bulk_strings() { - // basic valid cases - assert_eq!(parse_bulk_strings(b"$2\r\nok\r\n").unwrap().0, "ok"); - assert_eq!(parse_bulk_strings(b"$4\r\npong\r\n").unwrap().0, "pong"); - assert_eq!( - parse_bulk_strings(b"$11\r\nhello world\r\n").unwrap().0, - "hello world" - ); - - // empty string - assert_eq!(parse_bulk_strings(b"$0\r\n\r\n").unwrap().0, ""); - - // string with special characters (including \r and \n - allowed in bulk strings) - assert_eq!( - parse_bulk_strings(b"$13\r\nhello\r\nworld!\r\n").unwrap().0, - "hello\r\nworld!" - ); - - // string with various ascii characters - assert_eq!( - parse_bulk_strings(b"$30\r\n!@#$%^&*()_+-={}[]|\\:;\"'<>?,./\r\n") - .unwrap() - .0, - "!@#$%^&*()_+-={}[]|\\:;\"'<>?,./" - ); - - // large string - let large_content = "x".repeat(1000); - let large_bulk = format!("$1000\r\n{}\r\n", large_content); - if let RespType::BulkString(bulk) = parse_bulk_strings(large_bulk.as_bytes()).unwrap().0 - { - } - - assert_eq!( - parse_bulk_strings(large_bulk.as_bytes()).unwrap().0, - large_content - ); - - // string with only whitespace - assert_eq!(parse_bulk_strings(b"$3\r\n \r\n").unwrap().0, " "); - - // string with tabs and newlines - assert_eq!( - parse_bulk_strings(b"$7\r\nhe\tllo\n\r\n").unwrap().0, - "he\tllo\n" - ); - } - - #[test] - fn test_null_bulk_string() { - // Null bulk string - let (result, remaining) = parse_bulk_strings(b"$-1\r\n").unwrap(); - assert_eq!(result, RespType::Null()); - assert_eq!(remaining, b""); - - // Null bulk string with remaining data - let (result, remaining) = parse_bulk_strings(b"$-1\r\n+OK\r\n").unwrap(); - assert_eq!(result, RespType::Null()); - assert_eq!(remaining, b"+OK\r\n"); - } - - #[test] - fn test_invalid_bulk_strings() { - // Wrong data type marker - assert_eq!( - parse_bulk_strings(b"+OK\r\n").err().unwrap().message(), - "ERR Invalid data type" - ); - - // Invalid length format - assert_eq!( - parse_bulk_strings(b"$abc\r\n").err().unwrap().message(), - "ERR invalid value" - ); - - // Negative length (other than -1) - assert_eq!( - parse_bulk_strings(b"$-5\r\n").err().unwrap().message(), - "ERR invalid value" - ); - - // Missing length - assert_eq!( - parse_bulk_strings(b"$\r\n").err().unwrap().message(), - "ERR invalid value" - ); - - // Missing first \r\n after length - assert_eq!( - parse_bulk_strings(b"$5hello\r\n").err().unwrap().message(), - "ERR invalid value" - ); - - // Content shorter than declared length - assert_eq!( - parse_bulk_strings(b"$5\r\nhi\r\n").err().unwrap().message(), - "ERR Unexpected end of input" - ); - - // Content longer than declared length (missing final \r\n) - assert_eq!( - parse_bulk_strings(b"$2\r\nhello\r\n") - .err() - .unwrap() - .message(), - "ERR Unexpected end of input" - ); - - // Missing final \r\n - assert_eq!( - parse_bulk_strings(b"$5\r\nhello").err().unwrap().message(), - "ERR Unexpected end of input" - ); - - // Only \r without \n at the end - assert_eq!( - parse_bulk_strings(b"$5\r\nhello\r") - .err() - .unwrap() - .message(), - "ERR Unexpected end of input" - ); - - // Only \n without \r at the end - assert_eq!( - parse_bulk_strings(b"$5\r\nhello\n") - .err() - .unwrap() - .message(), - "ERR Unexpected end of input" - ); - - // Empty input - assert_eq!( - parse_bulk_strings(b"").err().unwrap().message(), - "ERR Empty data" - ); - - // Just the marker - assert_eq!( - parse_bulk_strings(b"$").err().unwrap().message(), - "ERR Unexpected end of input" - ); - - // Length too large for available data - assert_eq!( - parse_bulk_strings(b"$100\r\nshort\r\n") - .err() - .unwrap() - .message(), - "ERR Unexpected end of input" - ); - - // Zero length but with content - assert_eq!( - parse_bulk_strings(b"$0\r\nhello\r\n") - .err() - .unwrap() - .message(), - "ERR Unexpected end of input" - ); - } - - #[test] - fn test_bulk_string_remaining_bytes() { - // Test that remaining bytes are correctly returned - let (string, remaining) = parse_bulk_strings(b"$5\r\nhello\r\nnext data").unwrap(); - assert_eq!(string, "hello"); - assert_eq!(remaining, b"next data"); - - // Test with multiple commands - let (string, remaining) = parse_bulk_strings(b"$4\r\ntest\r\n:42\r\n").unwrap(); - assert_eq!(string, "test"); - assert_eq!(remaining, b":42\r\n"); - - // Test with no remaining data - let (string, remaining) = parse_bulk_strings(b"$3\r\nend\r\n").unwrap(); - assert_eq!(string, "end"); - assert_eq!(remaining, b""); - - // Test null string with remaining data - let (result, remaining) = parse_bulk_strings(b"$-1\r\n+PONG\r\n").unwrap(); - assert_eq!(result, RespType::Null()); - assert_eq!(remaining, b"+PONG\r\n"); - } - - #[test] - fn test_bulk_string_edge_cases() { - // String that contains the exact sequence that would end it - assert_eq!( - parse_bulk_strings(b"$8\r\ntest\r\n\r\n\r\n").unwrap().0, - "test\r\n" - ); - - // String with only \r\n - assert_eq!(parse_bulk_strings(b"$2\r\n\r\n\r\n").unwrap().0, "\r\n"); - - // String that starts with numbers - assert_eq!(parse_bulk_strings(b"$5\r\n12345\r\n").unwrap().0, "12345"); - - // String with control characters - assert_eq!( - parse_bulk_strings(b"$5\r\n\x01\x02\x03\x04\x05\r\n") - .unwrap() - .0, - "\x01\x02\x03\x04\x05" - ); - - // Maximum length value (within reason) - let content = "a".repeat(65535); - let bulk = format!("$65535\r\n{}\r\n", content); - assert_eq!(parse_bulk_strings(bulk.as_bytes()).unwrap().0, content); - } - } - - mod array_tests { - use super::*; - - #[test] - fn test_valid_arrays() { - // Simple array with strings - let arr = vec![ - RespType::BulkString("hello".into()), - RespType::BulkString("world".into()), - ]; - assert_eq!( - parse_array(b"*2\r\n$5\r\nhello\r\n$5\r\nworld\r\n") - .unwrap() - .0, - RespType::Array(arr) - ); - - // Array with mixed types - let arr = vec![ - RespType::Integer(42), - RespType::SimpleString("OK".to_string()), - RespType::Null(), - ]; - assert_eq!( - parse_array(b"*3\r\n:42\r\n+OK\r\n_\r\n").unwrap().0, - RespType::Array(arr) - ); - - // Nested array - let arr = vec![ - RespType::Array(vec![ - RespType::BulkString("nested".into()), - RespType::Integer(123), - ]), - RespType::SimpleError("ERR test".to_string()), - ]; - assert_eq! |
