use crate::{
client::RespLimits,
resp::{ParsedFrame, RespBuf, RespFrameParser, RespResponse, RespTapeMut, RespView},
};
fn parse(resp: &[u8]) -> crate::Result<(ParsedFrame, usize)> {
let mut tape = RespTapeMut::default();
RespFrameParser::new(resp, &mut tape).parse()
}
fn parse_response(resp: &[u8]) -> crate::Result<(RespResponse, usize)> {
let (frame, len) = parse(resp)?;
Ok((RespResponse::new(RespBuf::from_slice(resp), frame), len))
}
#[test]
fn parse_array() {
let resp = b"*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n"; let (response, len) = parse_response(resp).unwrap();
assert_eq!(22, len);
assert!(matches!(response.view(), Ok(RespView::Array(_))));
assert_eq!(
vec!["foo".to_owned(), "bar".to_owned()],
response.to::<Vec<String>>().unwrap()
);
}
#[test]
fn parse_null_array() {
let resp = b"*-1\r\n";
let (response, len) = parse_response(resp).unwrap();
assert_eq!(5, len);
assert!(matches!(response.view(), Ok(RespView::Null)));
}
#[test]
fn parse_negative_array_length_errors() {
let resp = b"*-2\r\n";
assert!(parse(resp).is_err());
}
#[test]
fn parse_negative_bulk_string_length_errors() {
let resp = b"$-2\r\n";
assert!(parse(resp).is_err());
}
#[test]
fn parse_negative_bulk_error_length_errors() {
let resp = b"!-2\r\n";
assert!(parse(resp).is_err());
}
#[test]
fn parse_negative_bulk_lengths_inside_collection_error() {
let resp = b"*1\r\n$-2\r\n";
assert!(parse(resp).is_err());
let resp = b"*1\r\n!-2\r\n";
assert!(parse(resp).is_err());
}
#[test]
fn parse_deeply_nested_frame_is_rejected_not_overflowing() {
let mut resp = b"*1\r\n".repeat(100_000);
resp.extend_from_slice(b":1\r\n");
assert!(parse(&resp).is_err());
}
#[test]
fn parse_nesting_within_limit_succeeds() {
let depth = 100;
let mut resp = b"*1\r\n".repeat(depth);
resp.extend_from_slice(b":7\r\n");
assert!(parse(&resp).is_ok());
}
#[test]
fn parse_oversized_bulk_string_length_is_rejected_before_payload() {
let resp = b"$536870913\r\n"; assert!(matches!(
parse(resp),
Err(crate::Error::Client(crate::ClientError::BulkLengthTooLarge))
));
}
#[test]
fn parse_oversized_collection_length_is_rejected() {
let resp = b"*134217729\r\n"; assert!(matches!(
parse(resp),
Err(crate::Error::Client(
crate::ClientError::CollectionLengthTooLarge
))
));
}
#[test]
fn parse_oversized_map_length_is_rejected() {
let resp = b"%67108865\r\n"; assert!(matches!(
parse(resp),
Err(crate::Error::Client(
crate::ClientError::CollectionLengthTooLarge
))
));
}
#[test]
fn parse_collection_length_wider_than_pointer_is_rejected() {
let resp = format!("*{}\r\n$3\r\nfoo\r\n", u64::from(u32::MAX) + 2);
assert!(matches!(
parse(resp.as_bytes()),
Err(crate::Error::Client(
crate::ClientError::CollectionLengthTooLarge
))
));
let resp = format!("%{}\r\n$3\r\nfoo\r\n$3\r\nbar\r\n", u64::from(u32::MAX) + 2);
assert!(matches!(
parse(resp.as_bytes()),
Err(crate::Error::Client(
crate::ClientError::CollectionLengthTooLarge
))
));
}
#[test]
fn parse_leading_attribute_is_skipped_and_reply_decodes() {
let resp = b"|1\r\n$3\r\nfoo\r\n$3\r\nbar\r\n:42\r\n";
let (response, len) = parse_response(resp).unwrap();
assert_eq!(resp.len(), len);
assert!(matches!(response.view(), Ok(RespView::Integer(42, _))));
}
#[test]
fn parse_attribute_preceding_an_array_element_is_skipped() {
let resp = b"*2\r\n:1\r\n|1\r\n$1\r\na\r\n$1\r\nb\r\n:2\r\n";
let (response, len) = parse_response(resp).unwrap();
assert_eq!(resp.len(), len);
assert!(matches!(response.view(), Ok(RespView::Array(_))));
assert_eq!(vec![1i64, 2], response.to::<Vec<i64>>().unwrap());
}
#[test]
fn an_attribute_header_with_a_negative_length_errors_as_a_map() {
assert!(matches!(
parse(b"|-1\r\n"),
Err(crate::Error::Client(crate::ClientError::CannotParseMap))
));
assert!(matches!(
parse(b"|-2\r\n"),
Err(crate::Error::Client(crate::ClientError::CannotParseMap))
));
}
#[test]
fn parse_big_number_is_exposed_as_its_string_payload() {
let resp = b"(3492890328409238509324850943850943825024385\r\n";
let (response, len) = parse_response(resp).unwrap();
assert_eq!(resp.len(), len);
assert!(matches!(
response.view(),
Ok(RespView::BulkString(
b"3492890328409238509324850943850943825024385"
))
));
}
#[test]
fn parse_map() {
let resp = b"%1\r\n$3\r\nfoo\r\n$3\r\nbar\r\n"; let (response, len) = parse_response(resp).unwrap();
assert_eq!(22, len);
assert!(matches!(response.view(), Ok(RespView::Map(_))));
let map = response
.to::<std::collections::HashMap<String, String>>()
.unwrap();
assert_eq!(1, map.len());
assert_eq!(Some(&"bar".to_owned()), map.get("foo"));
}
fn parse_with_limits(resp: &[u8], limits: RespLimits) -> crate::Result<(ParsedFrame, usize)> {
let mut tape = RespTapeMut::default();
RespFrameParser::with_limits(resp, &mut tape, limits).parse()
}
#[test]
fn a_lowered_nesting_limit_rejects_a_frame_the_default_accepts() {
let mut resp = b"*1\r\n".repeat(50);
resp.extend_from_slice(b":7\r\n");
assert!(parse(&resp).is_ok());
let limits = RespLimits {
max_nesting_depth: 10,
..Default::default()
};
assert!(matches!(
parse_with_limits(&resp, limits),
Err(crate::Error::Client(
crate::ClientError::MaxNestingDepthExceeded
))
));
}
#[test]
fn a_lowered_bulk_limit_rejects_a_length_the_default_accepts() {
let resp = b"$16\r\n0123456789abcdef\r\n";
assert!(parse(resp).is_ok());
let limits = RespLimits {
max_bulk_length: 8,
..Default::default()
};
assert!(matches!(
parse_with_limits(resp, limits),
Err(crate::Error::Client(crate::ClientError::BulkLengthTooLarge))
));
}
#[test]
fn a_lowered_collection_limit_rejects_a_cardinality_the_default_accepts() {
let resp = b"*3\r\n:1\r\n:2\r\n:3\r\n";
assert!(parse(resp).is_ok());
let limits = RespLimits {
max_collection_length: 2,
..Default::default()
};
assert!(matches!(
parse_with_limits(resp, limits),
Err(crate::Error::Client(
crate::ClientError::CollectionLengthTooLarge
))
));
}
#[test]
fn a_lowered_collection_limit_is_enforced_on_an_attribute_header() {
let resp = b"|1\r\n$1\r\na\r\n$1\r\nb\r\n:1\r\n";
assert!(parse(resp).is_ok());
let limits = RespLimits {
max_collection_length: 1,
..Default::default()
};
assert!(matches!(
parse_with_limits(resp, limits),
Err(crate::Error::Client(
crate::ClientError::CollectionLengthTooLarge
))
));
}
#[test]
fn a_lowered_nesting_limit_counts_depth_inside_an_attribute_payload() {
let within = b"|1\r\n$1\r\na\r\n*1\r\n*1\r\n:1\r\n:9\r\n";
let beyond = b"|1\r\n$1\r\na\r\n*1\r\n*1\r\n*1\r\n:1\r\n:9\r\n";
assert!(parse(within).is_ok());
assert!(parse(beyond).is_ok());
let limits = RespLimits {
max_nesting_depth: 3,
..Default::default()
};
assert!(parse_with_limits(within, limits).is_ok());
assert!(matches!(
parse_with_limits(beyond, limits),
Err(crate::Error::Client(
crate::ClientError::MaxNestingDepthExceeded
))
));
}
#[test]
fn a_raised_bulk_limit_reads_back_without_being_re_capped() {
let payload = "x".repeat(600);
let resp = format!("*1\r\n${}\r\n{payload}\r\n", payload.len()).into_bytes();
let limits = RespLimits {
max_bulk_length: 1024,
..Default::default()
};
let mut tape = RespTapeMut::default();
let (frame, len) = RespFrameParser::with_limits(&resp, &mut tape, limits)
.parse()
.unwrap();
assert_eq!(resp.len(), len);
let response = RespResponse::new(RespBuf::from_slice(&resp), frame);
assert_eq!(vec![payload], response.to::<Vec<String>>().unwrap());
}
#[test]
fn a_lowered_bulk_limit_is_enforced_inside_a_collection() {
let resp = b"*1\r\n$16\r\n0123456789abcdef\r\n";
assert!(parse(resp).is_ok());
let limits = RespLimits {
max_bulk_length: 8,
..Default::default()
};
assert!(matches!(
parse_with_limits(resp, limits),
Err(crate::Error::Client(crate::ClientError::BulkLengthTooLarge))
));
}