commands: split response tokenizer into separate file
This commit is contained in:
@@ -0,0 +1,311 @@
|
||||
pub type GenericResponseResult<'a> = Result<GenericResponse<'a>, &'a str>;
|
||||
|
||||
pub type GenericResponse<'a> = HashMap<&'a str, GenericResponseValue<'a>>;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum GenericResponseValue<'a> {
|
||||
Text(&'a str),
|
||||
Binary(&'a [u8]),
|
||||
// Many(Vec<GenericResponseValue<'a>>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ResponseAttributes<'a>(Vec<(&'a str, GenericResponseValue<'a>)>);
|
||||
|
||||
impl<'a> ResponseAttributes<'a> {
|
||||
pub fn new(raw: &'a str) -> Result<Self, ResponseParserError<'a>> {
|
||||
let mut parts = Vec::new();
|
||||
let mut lines = raw.lines();
|
||||
loop {
|
||||
let line = lines.next().ok_or(ResponseParserError::UnexpectedEOF)?;
|
||||
if line.is_empty() {
|
||||
println!("Warning: empty line in response");
|
||||
continue;
|
||||
}
|
||||
|
||||
if line == "OK" {
|
||||
break;
|
||||
}
|
||||
|
||||
let mut keyval = line.splitn(2, ": ");
|
||||
let key = keyval
|
||||
.next()
|
||||
.ok_or(ResponseParserError::SyntaxError(0, line))?;
|
||||
|
||||
// TODO: handle binary data, also verify binarylimit
|
||||
let value = keyval
|
||||
.next()
|
||||
.ok_or(ResponseParserError::SyntaxError(0, line))?;
|
||||
|
||||
parts.push((key, GenericResponseValue::Text(value)));
|
||||
}
|
||||
|
||||
Ok(parts.into())
|
||||
}
|
||||
|
||||
// pub fn get<'a>(&self, key: &str) -> Option<&GenericResponseValue<'a>> {
|
||||
// self.0.iter().find_map(|(k, v)| if k == &key { Some(v) } else { None })
|
||||
// }
|
||||
}
|
||||
|
||||
impl ResponseAttributes<'_> {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<HashMap<&'a str, GenericResponseValue<'a>>> for ResponseAttributes<'a> {
|
||||
fn from(map: HashMap<&'a str, GenericResponseValue<'a>>) -> Self {
|
||||
Self(map.into_iter().collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<ResponseAttributes<'a>> for HashMap<&'a str, GenericResponseValue<'a>> {
|
||||
fn from(val: ResponseAttributes<'a>) -> Self {
|
||||
debug_assert!({
|
||||
let mut uniq = HashSet::new();
|
||||
val.0.iter().all(move |x| uniq.insert(*x))
|
||||
});
|
||||
|
||||
val.0.into_iter().collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<ResponseAttributes<'a>> for Vec<(&'a str, GenericResponseValue<'a>)> {
|
||||
fn from(val: ResponseAttributes<'a>) -> Self {
|
||||
val.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Vec<(&'a str, GenericResponseValue<'a>)>> for ResponseAttributes<'a> {
|
||||
fn from(val: Vec<(&'a str, GenericResponseValue<'a>)>) -> Self {
|
||||
Self(val)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: There should probably be a helper that lets you extract and verify one, two or maybe
|
||||
// three properties without having to allocate a hashmap to get a nice API. We can retrieve
|
||||
// the properties by name with a loop on the inner vec.
|
||||
|
||||
/*******************/
|
||||
/* Parsing Helpers */
|
||||
/*******************/
|
||||
|
||||
macro_rules! _expect_property_type {
|
||||
($property:expr, $name:expr, $variant:ident) => {
|
||||
match $property {
|
||||
Some(crate::response_tokenizer::GenericResponseValue::$variant(value)) => Some(value),
|
||||
Some(value) => {
|
||||
let actual_type = match value {
|
||||
crate::response_tokenizer::GenericResponseValue::Text(_) => "Text",
|
||||
crate::response_tokenizer::GenericResponseValue::Binary(_) => "Binary",
|
||||
};
|
||||
return Err(
|
||||
crate::commands::ResponseParserError::UnexpectedPropertyType(
|
||||
$name,
|
||||
actual_type,
|
||||
),
|
||||
);
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! _parse_optional_property_type {
|
||||
($name:expr, $property:expr) => {
|
||||
$property
|
||||
.map(|value| {
|
||||
value.parse().map_err(|_| {
|
||||
crate::commands::ResponseParserError::InvalidProperty($name, value)
|
||||
})
|
||||
})
|
||||
.transpose()?
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! _unwrap_optional_property_type {
|
||||
($name:expr, $property:expr) => {
|
||||
match $property {
|
||||
Some(value) => value,
|
||||
None => return Err(crate::commands::ResponseParserError::MissingProperty($name)),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! expect_optional_property_type {
|
||||
($property:expr, $name:expr, $variant:ident) => {
|
||||
crate::response_tokenizer::_expect_property_type!($property, $name, $variant)
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! expect_property_type {
|
||||
($property:expr, $name:expr, $variant:ident) => {{
|
||||
let prop = crate::response_tokenizer::_expect_property_type!($property, $name, $variant);
|
||||
crate::response_tokenizer::_unwrap_optional_property_type!($name, prop)
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! get_optional_property {
|
||||
($parts:expr, $name:literal, $variant:ident) => {
|
||||
crate::response_tokenizer::_expect_property_type!(
|
||||
{ $parts.get($name).map(|v| *v) },
|
||||
$name,
|
||||
$variant
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! get_property {
|
||||
($parts:expr, $name:literal, $variant:ident) => {{
|
||||
let prop = crate::response_tokenizer::_expect_property_type!(
|
||||
{ $parts.get($name).map(|v| *v) },
|
||||
$name,
|
||||
$variant
|
||||
);
|
||||
crate::response_tokenizer::_unwrap_optional_property_type!($name, prop)
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! get_and_parse_optional_property {
|
||||
($parts:ident, $name:literal, $variant:ident) => {{
|
||||
let prop = crate::response_tokenizer::_expect_property_type!(
|
||||
{ $parts.get($name).map(|v| *v) },
|
||||
$name,
|
||||
$variant
|
||||
);
|
||||
crate::response_tokenizer::_parse_optional_property_type!($name, prop)
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! get_and_parse_property {
|
||||
($parts:ident, $name:literal, $variant:ident) => {{
|
||||
let prop = crate::response_tokenizer::_expect_property_type!(
|
||||
{ $parts.get($name).map(|v| *v) },
|
||||
$name,
|
||||
$variant
|
||||
);
|
||||
let prop = crate::response_tokenizer::_parse_optional_property_type!($name, prop);
|
||||
crate::response_tokenizer::_unwrap_optional_property_type!($name, prop)
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! get_next_optional_property {
|
||||
($parts:ident, $variant:ident) => {
|
||||
match $parts.next() {
|
||||
Some((name, value)) => {
|
||||
crate::response_tokenizer::_expect_property_type!({ Some(value) }, name, $variant)
|
||||
.map(|value| (name, value))
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! get_next_property {
|
||||
($parts:ident, $variant:ident) => {
|
||||
match $parts.next() {
|
||||
Some((name, value)) => (
|
||||
name,
|
||||
crate::response_tokenizer::_expect_property_type!({ Some(value) }, name, $variant)
|
||||
.unwrap(),
|
||||
),
|
||||
None => return Err(crate::commands::ResponseParserError::UnexpectedEOF),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! get_next_and_parse_optional_property {
|
||||
($parts:ident, $variant:ident) => {
|
||||
match $parts.next() {
|
||||
Some((name, value)) => {
|
||||
let prop = crate::response_tokenizer::_expect_property_type!(
|
||||
{ Some(value) },
|
||||
name,
|
||||
$variant
|
||||
);
|
||||
prop.map(|value| {
|
||||
(
|
||||
name,
|
||||
crate::response_tokenizer::_parse_optional_property_type!(name, value),
|
||||
)
|
||||
})
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! get_next_and_parse_property {
|
||||
($parts:ident, $variant:ident) => {
|
||||
match $parts.next() {
|
||||
Some((name, value)) => {
|
||||
let prop = crate::response_tokenizer::_expect_property_type!(
|
||||
{ Some(value) },
|
||||
name,
|
||||
$variant
|
||||
);
|
||||
let prop = crate::response_tokenizer::_parse_optional_property_type!(name, prop);
|
||||
(
|
||||
name,
|
||||
crate::response_tokenizer::_unwrap_optional_property_type!(name, prop),
|
||||
)
|
||||
}
|
||||
None => return Err(crate::commands::ResponseParserError::UnexpectedEOF),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
pub(crate) use _expect_property_type;
|
||||
pub(crate) use _parse_optional_property_type;
|
||||
pub(crate) use _unwrap_optional_property_type;
|
||||
pub(crate) use expect_property_type;
|
||||
// pub(crate) use expect_optional_property_type;
|
||||
pub(crate) use get_and_parse_optional_property;
|
||||
pub(crate) use get_and_parse_property;
|
||||
// pub(crate) use get_next_and_parse_optional_property;
|
||||
pub(crate) use get_next_and_parse_property;
|
||||
// pub(crate) use get_next_optional_property;
|
||||
pub(crate) use get_next_property;
|
||||
pub(crate) use get_optional_property;
|
||||
pub(crate) use get_property;
|
||||
|
||||
use crate::commands::ResponseParserError;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[cfg(debug_assertions)]
|
||||
fn test_valid_hashmap_uniqueness_assert() {
|
||||
let valid_maplike_attrs: ResponseAttributes = vec![
|
||||
("a", GenericResponseValue::Text("1")),
|
||||
("A", GenericResponseValue::Text("2")),
|
||||
("A ", GenericResponseValue::Text("3")),
|
||||
("b", GenericResponseValue::Text("4")),
|
||||
]
|
||||
.into();
|
||||
|
||||
let map: HashMap<_, _> = valid_maplike_attrs.into();
|
||||
assert_eq!(map.len(), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(debug_assertions)]
|
||||
#[should_panic]
|
||||
fn test_invalid_hashmap_uniqueness_assert() {
|
||||
let invalid_maplike_attrs: ResponseAttributes = vec![
|
||||
("a", GenericResponseValue::Text("1")),
|
||||
("b", GenericResponseValue::Text("2")),
|
||||
("c", GenericResponseValue::Text("3")),
|
||||
("a", GenericResponseValue::Text("4")),
|
||||
]
|
||||
.into();
|
||||
|
||||
let map: HashMap<_, _> = invalid_maplike_attrs.into();
|
||||
assert_eq!(map.len(), 4);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user