aboutsummaryrefslogtreecommitdiff
path: root/src/resp_parser.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/resp_parser.rs')
-rw-r--r--src/resp_parser.rs1123
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!