use crate::terrapipe::RespCode;
pub type DataGroup = Vec<DataType>;
#[derive(Debug, PartialEq)]
#[non_exhaustive]
pub enum DataType {
Str(String),
RespCode(RespCode),
UnsignedInt(u64),
}
#[non_exhaustive]
enum _DataType {
Str(Option<String>),
RespCode(Option<RespCode>),
UnsignedInt(Option<Result<u64, std::num::ParseIntError>>),
}
#[derive(Debug, PartialEq)]
pub enum ClientResult {
InvalidResponse,
PipelinedResponse(Vec<DataGroup>, usize),
SimpleResponse(DataGroup, usize),
ResponseItem(DataType, usize),
Empty,
Incomplete,
ParseError,
}
pub fn parse(buf: &[u8]) -> ClientResult {
if buf.len() < 6 {
return ClientResult::Incomplete;
}
let mut pos = 0;
if buf[pos] != b'#' {
return ClientResult::InvalidResponse;
} else {
pos += 1;
}
let next_line = match read_line_and_return_next_line(&mut pos, &buf) {
Some(line) => line,
None => {
return ClientResult::Incomplete;
}
};
pos += 1; let mut action_size = 0usize;
if next_line[0] == b'*' {
let mut line_iter = next_line.into_iter().skip(1).peekable();
while let Some(dig) = line_iter.next() {
let curdig: usize = match dig.checked_sub(48) {
Some(dig) => {
if dig > 9 {
return ClientResult::InvalidResponse;
} else {
dig.into()
}
}
None => return ClientResult::InvalidResponse,
};
action_size = (action_size * 10) + curdig;
}
} else {
return ClientResult::InvalidResponse;
}
let mut items: Vec<DataGroup> = Vec::with_capacity(action_size);
while pos < buf.len() && items.len() <= action_size {
match buf[pos] {
b'#' => {
pos += 1; let next_line = match read_line_and_return_next_line(&mut pos, &buf) {
Some(line) => line,
None => {
return ClientResult::Incomplete;
}
}; pos += 1; match next_line[0] {
b'&' => {
let mut current_array_size = 0usize;
let mut linepos = 1; while linepos < next_line.len() {
let curdg: usize = match next_line[linepos].checked_sub(48) {
Some(dig) => {
if dig > 9 {
return ClientResult::InvalidResponse;
} else {
dig.into()
}
}
None => return ClientResult::InvalidResponse,
};
current_array_size = (current_array_size * 10) + curdg; linepos += 1; }
let mut actiongroup: Vec<DataType> = Vec::with_capacity(current_array_size);
while pos < buf.len() && actiongroup.len() < current_array_size {
let mut element_size = 0usize;
let datatype = match buf[pos] {
b'+' => _DataType::Str(None),
b'!' => _DataType::RespCode(None),
b':' => _DataType::UnsignedInt(None),
x @ _ => unimplemented!("Type '{}' not implemented", char::from(x)),
};
pos += 1; while pos < buf.len() && buf[pos] != b'\n' {
let curdig: usize = match buf[pos].checked_sub(48) {
Some(dig) => {
if dig > 9 {
return ClientResult::InvalidResponse;
} else {
dig.into()
}
}
None => return ClientResult::InvalidResponse,
};
element_size = (element_size * 10) + curdig; pos += 1; }
pos += 1;
let mut value = String::with_capacity(element_size);
let extracted = match buf.get(pos..pos + element_size) {
Some(s) => s,
None => return ClientResult::Incomplete,
};
pos += element_size; value.push_str(&String::from_utf8_lossy(extracted));
pos += 1; actiongroup.push(match datatype {
_DataType::Str(_) => DataType::Str(value),
_DataType::RespCode(_) => {
DataType::RespCode(RespCode::from_str(&value))
}
_DataType::UnsignedInt(_) => {
if let Ok(unsigned_int64) = value.parse() {
DataType::UnsignedInt(unsigned_int64)
} else {
return ClientResult::ParseError;
}
}
});
}
items.push(actiongroup);
}
_ => return ClientResult::InvalidResponse,
}
continue;
}
_ => {
return ClientResult::InvalidResponse;
}
}
}
if buf.get(pos).is_none() {
if items.len() == action_size {
if items.len() == 1 {
if items[0].len() == 1 {
ClientResult::ResponseItem(items.swap_remove(0).swap_remove(0), pos)
} else {
ClientResult::SimpleResponse(items.swap_remove(0), pos)
}
} else {
ClientResult::PipelinedResponse(items, pos)
}
} else {
ClientResult::Incomplete
}
} else {
ClientResult::InvalidResponse
}
}
fn read_line_and_return_next_line<'a>(pos: &mut usize, buf: &'a [u8]) -> Option<&'a [u8]> {
let mut next_line_size = 0usize;
while pos < &mut buf.len() && buf[*pos] != b'\n' {
let curdig: usize = match buf[*pos].checked_sub(48) {
Some(dig) => {
if dig > 9 {
return None;
} else {
dig.into()
}
}
None => return None,
};
next_line_size = (next_line_size * 10) + curdig; *pos += 1; }
*pos += 1; let next_line = match buf.get(*pos..*pos + next_line_size) {
Some(line) => line,
None => {
return None;
}
}; *pos += next_line_size;
Some(next_line)
}
#[cfg(test)]
#[test]
fn test_deserializer_responseitem() {
let res = "#2\n*1\n#2\n&1\n+4\nHEY!\n".as_bytes().to_owned();
assert_eq!(
parse(&res),
ClientResult::ResponseItem(DataType::Str("HEY!".to_owned()), res.len())
);
let res = "#2\n*1\n#2\n&1\n!1\n0\n".as_bytes().to_owned();
assert_eq!(
parse(&res),
ClientResult::ResponseItem(DataType::RespCode(RespCode::Okay), res.len())
);
}
#[cfg(test)]
#[test]
fn test_deserializer_simple_response() {
let res = "#2\n*1\n#2\n&5\n!1\n1\n!1\n0\n+5\nsayan\n+2\nis\n+4\nbusy\n"
.as_bytes()
.to_owned();
let ret = parse(&res);
assert_eq!(
ret,
ClientResult::SimpleResponse(
vec![
DataType::RespCode(RespCode::NotFound),
DataType::RespCode(RespCode::Okay),
DataType::Str("sayan".to_owned()),
DataType::Str("is".to_owned()),
DataType::Str("busy".to_owned())
],
res.len()
)
);
if let ClientResult::SimpleResponse(ret, _) = ret {
for val in ret {
let _ = format!("{:?}", val);
}
} else {
panic!("Expected a SimpleResponse with a single datagroup")
}
}