redust_resp/
parser.rs

1use std::{borrow::Cow, str::from_utf8};
2
3use nom::{
4	branch::alt,
5	bytes::streaming::take,
6	character::streaming::{char, crlf, i64, not_line_ending},
7	combinator::{map, map_res},
8	error::ErrorKind,
9	sequence::{delimited, terminated},
10	IResult,
11};
12
13/// A parser error.
14pub type Error<'a> = nom::Err<nom::error::Error<Cow<'a, [u8]>>>;
15pub(crate) type RawError<'a> = nom::Err<nom::error::Error<&'a [u8]>>;
16
17/// Parse a RESP string.
18pub fn parse_str(data: &[u8]) -> IResult<&[u8], &str> {
19	map_res(delimited(char('+'), not_line_ending, crlf), from_utf8)(data)
20}
21
22/// Parse a RESP error.
23pub fn parse_err(data: &[u8]) -> IResult<&[u8], &str> {
24	map_res(delimited(char('-'), not_line_ending, crlf), from_utf8)(data)
25}
26
27/// Parse a RESP integer.
28pub fn parse_int(data: &[u8]) -> IResult<&[u8], i64> {
29	delimited(char(':'), i64, crlf)(data)
30}
31
32/// Parse a RESP bulk string.
33pub fn parse_bytes(data: &[u8]) -> IResult<&[u8], Option<&[u8]>> {
34	let (data, len) = delimited(char('$'), i64, crlf)(data)?;
35	Ok(match len {
36		-1 => (data, None),
37		0.. => map(terminated(take(len as usize), crlf), Some)(data)?,
38		_ => {
39			return Err(nom::Err::Failure(nom::error::Error::new(
40				data,
41				ErrorKind::Digit,
42			)))
43		}
44	})
45}
46
47/// Parse the length of a RESP array. Parsing the array elements is handled handled by the other
48/// parsers.
49pub fn parse_array(data: &[u8]) -> IResult<&[u8], i64> {
50	delimited(char('*'), i64, crlf)(data)
51}
52
53/// Parse a RESP string, including bulk string if the bytes are valid UTF-8.
54pub fn parse_str_loose(data: &[u8]) -> IResult<&[u8], &str> {
55	alt((
56		parse_str,
57		map_res(map(parse_bytes, Option::unwrap_or_default), from_utf8),
58	))(data)
59}
60
61/// Parse a RESP integer, including strings and bulk strings if they are valid integers.
62pub fn parse_int_loose(data: &[u8]) -> IResult<&[u8], i64> {
63	alt((parse_int, map_res(parse_str_loose, str::parse)))(data)
64}
65
66#[cfg(test)]
67mod test {
68	use super::*;
69
70	#[test]
71	fn test_parse_str() {
72		let resp = "+OK\r\n".as_bytes();
73		let (rem, res) = parse_str(resp).expect("Parsed string");
74
75		assert_eq!(0, rem.len());
76		assert_eq!("OK", res);
77	}
78
79	#[test]
80	fn test_parse_int() {
81		let resp = ":10\r\n".as_bytes();
82		let (rem, res) = parse_int(resp).expect("Parsed int");
83
84		assert_eq!(0, rem.len());
85		assert_eq!(10, res);
86	}
87
88	#[test]
89	fn test_parse_bytes() {
90		let resp = "$6\r\nfoobar\r\n".as_bytes();
91		let (rem, res) = parse_bytes(resp).expect("Parsed bytes");
92
93		assert_eq!(0, rem.len());
94		assert_eq!(Some("foobar".as_bytes()), res);
95	}
96
97	#[test]
98	fn test_parse_empty_bytes() {
99		let resp = "$0\r\n\r\n".as_bytes();
100		let (rem, res) = parse_bytes(resp).expect("Parsed bytes");
101
102		assert_eq!(0, rem.len());
103		assert_eq!(Some([].as_slice()), res);
104	}
105
106	#[test]
107	fn test_parse_null_bytes() {
108		let resp = "$-1\r\n".as_bytes();
109		let (rem, res) = parse_bytes(resp).expect("Parsed bytes");
110
111		assert_eq!(0, rem.len());
112		assert_eq!(None, res);
113	}
114
115	#[test]
116	fn test_parse_array() {
117		let resp = "*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n".as_bytes();
118		let (rem, res) = parse_array(resp).expect("Parsed bytes");
119
120		assert_eq!(18, rem.len());
121		assert_eq!(2, res);
122	}
123
124	#[test]
125	fn test_parse_empty_array() {
126		let resp = "*0\r\n".as_bytes();
127		let (rem, res) = parse_array(resp).expect("Parsed bytes");
128
129		assert_eq!(0, rem.len());
130		assert_eq!(0, res);
131	}
132
133	#[test]
134	fn test_parse_null_array() {
135		let resp = "*-1\r\n".as_bytes();
136		let (rem, res) = parse_array(resp).expect("Parsed bytes");
137
138		assert_eq!(0, rem.len());
139		assert_eq!(-1, res);
140	}
141}