use crate::{
ClientError, Error, Result,
resp::{RespBuf, RespFrameParser, RespResponse, RespTape, RespTapeMut, RespView},
};
use bytes::Bytes;
fn parse_owned(resp: &[u8]) -> RespResponse {
let resp = Bytes::copy_from_slice(resp);
let mut tape = RespTapeMut::default();
let mut parser = RespFrameParser::new(&resp, &mut tape);
let (frame, _) = parser.parse().unwrap();
RespResponse::new(resp.into(), frame)
}
fn parse(resp: &'static [u8]) -> RespResponse {
let resp = Bytes::from_static(resp);
let mut tape = RespTapeMut::default();
let mut parser = RespFrameParser::new(&resp, &mut tape);
let (frame, _) = parser.parse().unwrap();
RespResponse::new(resp.into(), frame)
}
#[test]
fn array() -> Result<()> {
let resp = Bytes::from_static(b"*6\r\n$4\r\nelt1\r\n$4\r\nelt2\r\n$4\r\nelt3\r\n$4\r\nelt4\r\n$4\r\nelt5\r\n$4\r\nelt6\r\n"); let mut tape = RespTapeMut::default();
let mut parser = RespFrameParser::new(&resp, &mut tape);
let (frame, _) = parser.parse()?;
let response = RespResponse::new(resp.into(), frame);
let view = response.view()?;
assert!(matches!(view, RespView::Array(_)));
let RespView::Array(array) = view else {
unreachable!()
};
let mut it = array.into_iter();
assert_eq!(RespView::BulkString(b"elt1"), it.next().unwrap()?);
assert_eq!(RespView::BulkString(b"elt2"), it.next().unwrap()?);
assert_eq!(RespView::BulkString(b"elt3"), it.next().unwrap()?);
assert_eq!(RespView::BulkString(b"elt4"), it.next().unwrap()?);
assert_eq!(RespView::BulkString(b"elt5"), it.next().unwrap()?);
assert_eq!(RespView::BulkString(b"elt6"), it.next().unwrap()?);
assert!(it.next().is_none());
Ok(())
}
#[test]
fn into_collection_iter() {
let resp = Bytes::from_static(b"*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n");
let mut tape = RespTapeMut::default();
let mut parser = RespFrameParser::new(&resp, &mut tape);
let (frame, _) = parser.parse().unwrap();
let response = RespResponse::new(resp.into(), frame);
let mut iter = response.into_collection_iter().unwrap();
assert_eq!(
RespView::BulkString(b"foo"),
iter.next().unwrap().unwrap().view().unwrap()
);
assert_eq!(
RespView::BulkString(b"bar"),
iter.next().unwrap().unwrap().view().unwrap()
);
assert!(iter.next().is_none());
}
#[test]
fn debug_renders_the_reply_without_the_tape() {
let response = parse(b"*2\r\n$3\r\nfoo\r\n:1\r\n");
let rendered = format!("{response:?}");
assert_eq!(r#"Array([BulkString("foo"), Integer(1)])"#, rendered);
}
#[test]
fn debug_renders_a_map_as_key_value_pairs() {
let response = parse(b"%2\r\n$2\r\nid\r\n:12\r\n$4\r\nmode\r\n$7\r\ncluster\r\n");
let rendered = format!("{response:?}");
assert_eq!(
r#"Map({BulkString("id"): Integer(12), BulkString("mode"): BulkString("cluster")})"#,
rendered
);
}
#[test]
fn debug_renders_nested_collections() {
let response = parse(b"*2\r\n*2\r\n:1\r\n:2\r\n~1\r\n$1\r\na\r\n");
let rendered = format!("{response:?}");
assert_eq!(
r#"Array([Array([Integer(1), Integer(2)]), Set([BulkString("a")])])"#,
rendered
);
}
#[test]
fn debug_renders_a_bufferless_scalar() {
assert_eq!("Integer(42)", format!("{:?}", RespResponse::integer(42)));
assert_eq!("Null", format!("{:?}", RespResponse::null()));
assert_eq!("SimpleString(\"OK\")", format!("{:?}", RespResponse::ok()));
}
#[test]
fn debug_truncates_a_large_reply() {
let mut resp = format!("*{}\r\n", 1000).into_bytes();
for _ in 0..1000 {
resp.extend_from_slice(b"$10\r\nelement123\r\n");
}
let resp = Bytes::from(resp);
let mut tape = RespTapeMut::default();
let mut parser = RespFrameParser::new(&resp, &mut tape);
let (frame, _) = parser.parse().unwrap();
let response = RespResponse::new(resp.into(), frame);
let rendered = format!("{response:?}");
assert!(rendered.ends_with("<truncated>"), "got: {rendered}");
assert!(
rendered.len() < 1100,
"rendering too long: {}",
rendered.len()
);
}
#[test]
fn a_parsed_frame_reports_its_shape_instead_of_the_tape() {
let resp = Bytes::from_static(b"*2\r\n$3\r\nfoo\r\n:1\r\n");
let mut tape = RespTapeMut::default();
let mut parser = RespFrameParser::new(&resp, &mut tape);
let (frame, _) = parser.parse().unwrap();
assert_eq!("Collection { nodes: 4 }", format!("{frame:?}"));
let mut tape = RespTapeMut::default();
let (frame, _) = RespFrameParser::new(b":12\r\n", &mut tape).parse().unwrap();
assert_eq!("Scalar { at: 0 }", format!("{frame:?}"));
}
#[test]
fn into_collection_iter_beyond_inline_ranges() {
let resp = Bytes::from_static(
b"*8\r\n$4\r\nelt1\r\n$4\r\nelt2\r\n$4\r\nelt3\r\n$4\r\nelt4\r\n$4\r\nelt5\r\n$4\r\nelt6\r\n$4\r\nelt7\r\n$4\r\nelt8\r\n",
);
let mut tape = RespTapeMut::default();
let mut parser = RespFrameParser::new(&resp, &mut tape);
let (frame, _) = parser.parse().unwrap();
let response = RespResponse::new(resp.into(), frame);
let iter = response.into_collection_iter().unwrap();
let values: Vec<_> = iter
.map(|r| match r.unwrap().view().unwrap() {
RespView::BulkString(b) => b.to_vec(),
other => panic!("unexpected view: {other:?}"),
})
.collect();
assert_eq!(
vec![
b"elt1".to_vec(),
b"elt2".to_vec(),
b"elt3".to_vec(),
b"elt4".to_vec(),
b"elt5".to_vec(),
b"elt6".to_vec(),
b"elt7".to_vec(),
b"elt8".to_vec(),
],
values
);
}
#[test]
fn into_collection_iter_accepts_every_collection_tag() {
for resp in [
b"*2\r\n:1\r\n:2\r\n".as_slice(),
b"%1\r\n:1\r\n:2\r\n".as_slice(),
b"~2\r\n:1\r\n:2\r\n".as_slice(),
b">2\r\n:1\r\n:2\r\n".as_slice(),
] {
let response = parse_owned(resp);
let values: Vec<i64> = response
.into_collection_iter()
.unwrap()
.map(|r| r.unwrap().to::<i64>().unwrap())
.collect();
assert_eq!(vec![1i64, 2], values, "failed on {}", resp[0] as char);
}
assert!(parse_owned(b":1\r\n").into_collection_iter().is_err());
}
#[test]
fn into_collection_iter_on_an_error_reply_yields_the_redis_error() {
let err = parse_owned(b"-ERR nope\r\n").into_collection_iter();
assert!(matches!(err, Err(crate::Error::Redis(_))));
}
#[test]
fn a_synthesized_response_reads_back_its_value() {
assert_eq!(42i64, RespResponse::integer(42).to::<i64>().unwrap());
assert_eq!(None, RespResponse::null().to::<Option<String>>().unwrap());
assert_eq!("OK", RespResponse::ok().to::<String>().unwrap());
assert_eq!(
vec![1i64, 2],
RespResponse::integer_array(vec![1, 2])
.to::<Vec<i64>>()
.unwrap()
);
assert_eq!(
vec![1i64, 2],
RespResponse::owned_array(vec![RespResponse::integer(1), RespResponse::integer(2)])
.to::<Vec<i64>>()
.unwrap()
);
}
#[test]
fn compact_preserves_the_value_it_copies_out() {
let scalar = parse_owned(b"$5\r\nhello\r\n");
assert_eq!("hello", scalar.compact().to::<String>().unwrap());
let integer = parse_owned(b":12\r\n");
assert_eq!(12i64, integer.compact().to::<i64>().unwrap());
let double = parse_owned(b",12.5\r\n");
assert_eq!(12.5f64, double.compact().to::<f64>().unwrap());
let null = parse_owned(b"_\r\n");
assert_eq!(None, null.compact().to::<Option<String>>().unwrap());
let collection = parse_owned(b"*2\r\n$3\r\nfoo\r\n:1\r\n");
assert_eq!(
r#"Array([BulkString("foo"), Integer(1)])"#,
format!("{:?}", collection.compact())
);
let synthesized = RespResponse::owned_array(vec![RespResponse::integer(7)]);
assert_eq!(vec![7i64], synthesized.compact().to::<Vec<i64>>().unwrap());
}
fn tapeless_frame(resp: &'static [u8]) -> RespResponse {
RespResponse::Frame {
buf: RespBuf::from(Bytes::from_static(resp)),
tape: RespTape::default(),
root: 0,
}
}
#[test]
fn a_tapeless_frame_reads_the_scalar_its_own_bytes_hold() {
let response = tapeless_frame(b"+OK\r\n");
assert!(matches!(response.view(), Ok(RespView::SimpleString(b"OK"))));
let response = tapeless_frame(b"$-1\r\n");
assert!(matches!(response.view(), Ok(RespView::Null)));
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "must end at its scalar's terminator")]
fn a_tapeless_frame_carrying_bytes_past_its_scalar_trips_the_invariant() {
let _ = tapeless_frame(b"+OK\r\n\r\n").view();
}
#[test]
fn a_malformed_tapeless_frame_is_rejected_rather_than_misread() {
assert!(matches!(
tapeless_frame(b"#x\r\n").view(),
Err(Error::Client(ClientError::CannotParseBoolean))
));
assert!(matches!(
tapeless_frame(b"$-2\r\n").view(),
Err(Error::Client(ClientError::CannotParseBulkString))
));
assert!(matches!(
tapeless_frame(b"=2\r\nab\r\n").view(),
Err(Error::Client(ClientError::VerbatimStringTooShort))
));
}