commands: split response tokenizer into separate file

This commit is contained in:
2025-11-24 19:16:09 +09:00
parent f481fd6636
commit ea8a0dbf72
135 changed files with 687 additions and 839 deletions
+311
View File
@@ -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);
}
}