commands: handle some more response parsing

This commit is contained in:
Oystein Kristoffer Tveit 2024-11-30 03:28:22 +01:00
parent 184f9fc215
commit 4fada75fe9
Signed by: oysteikt
GPG Key ID: 9F2F7D8250F35146
3 changed files with 123 additions and 20 deletions

View File

@ -36,15 +36,12 @@ pub trait Command {
Self::parse_request(parts).map(|(req, _)| (req, rest))
}
// TODO: Replace the HashMap datastructure with something that allows keeping
// duplicate keys and order of insertion
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>>;
fn parse_raw_response(raw: &str) -> Result<Self::Response, ResponseParserError<'_>> {
// TODO: Fix the implementation to use ResponseAttributes natively
let mut parts = HashMap::new();
let mut parts = Vec::new();
let mut lines = raw.lines();
loop {
let line = lines.next().ok_or(ResponseParserError::UnexpectedEOF)?;
@ -61,11 +58,13 @@ pub trait Command {
let key = keyval
.next()
.ok_or(ResponseParserError::SyntaxError(0, line))?;
// TODO: handle binary data
let value = keyval
.next()
.ok_or(ResponseParserError::SyntaxError(0, line))?;
parts.insert(key, GenericResponseValue::Text(value));
parts.push((key, GenericResponseValue::Text(value)));
}
Self::parse_response(parts.into())
@ -98,7 +97,7 @@ pub type GenericResponseResult<'a> = Result<GenericResponse<'a>, &'a str>;
pub type GenericResponse<'a> = HashMap<&'a str, GenericResponseValue<'a>>;
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum GenericResponseValue<'a> {
Text(&'a str),
Binary(&'a [u8]),
@ -132,6 +131,18 @@ impl<'a> From<ResponseAttributes<'a>> for HashMap<&'a str, GenericResponseValue<
}
}
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)
}
}
/*******************/
/* Parsing Helpers */
/*******************/

View File

@ -1,5 +1,6 @@
use crate::commands::{
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
Command, GenericResponseValue, Request, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
pub struct Channels;
@ -22,17 +23,47 @@ impl Command for Channels {
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
todo!()
// let channels = parts
// .get("channels")
// .ok_or(ResponseParserError::MissingField("channels"))?
// .as_list()
// .ok_or(ResponseParserError::UnexpectedType("channels", "list"))?
// .iter()
// .map(|v| v.as_text().map(ToOwned::to_owned))
// .collect::<Option<Vec<_>>>()
// .ok_or(ResponseParserError::UnexpectedType("channels", "text"))?;
let parts: Vec<_> = parts.into();
let mut channel_names = Vec::with_capacity(parts.len());
for (key, value) in parts {
debug_assert!(key == "channels");
let channel_name = match value {
GenericResponseValue::Text(s) => s,
GenericResponseValue::Binary(_) => {
return Err(ResponseParserError::UnexpectedPropertyType(
"channels", "Binary",
));
}
};
channel_names.push(channel_name.to_string());
}
// Ok(ChannelsResponse { channels })
Ok(ChannelsResponse {
channels: channel_names,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use indoc::indoc;
#[test]
fn test_parse_response() {
let response = indoc! {"
channels: foo
channels: bar
channels: baz
OK
"};
let response = Channels::parse_raw_response(response).unwrap();
assert_eq!(
response,
ChannelsResponse {
channels: vec!["foo".to_string(), "bar".to_string(), "baz".to_string()]
}
);
}
}

View File

@ -1,5 +1,6 @@
use crate::commands::{
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
Command, GenericResponseValue, Request, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
pub struct ReadMessages;
@ -22,6 +23,66 @@ impl Command for ReadMessages {
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
todo!()
let parts: Vec<_> = parts.into();
debug_assert!(parts.len() % 2 == 0);
let mut messages = Vec::with_capacity(parts.len() / 2);
for channel_message_pair in parts.chunks_exact(2) {
let (ckey, cvalue) = channel_message_pair[0];
let (mkey, mvalue) = channel_message_pair[1];
debug_assert!(ckey == "channel");
debug_assert!(mkey == "message");
let channel = match cvalue {
GenericResponseValue::Text(s) => s.to_string(),
GenericResponseValue::Binary(_) => {
return Err(ResponseParserError::UnexpectedPropertyType(
"channel", "Binary",
))
}
};
let message = match mvalue {
GenericResponseValue::Text(s) => s.to_string(),
GenericResponseValue::Binary(_) => {
return Err(ResponseParserError::UnexpectedPropertyType(
"message", "Binary",
))
}
};
messages.push((channel, message));
}
Ok(ReadMessagesResponse { messages })
}
}
#[cfg(test)]
mod tests {
use indoc::indoc;
use super::*;
#[test]
fn test_parse_response() {
let input = indoc! {"
channel: channel1
message: message1
channel: channel2
message: message2
OK
"};
let result = ReadMessages::parse_raw_response(input);
assert_eq!(
result,
Ok(ReadMessagesResponse {
messages: vec![
("channel1".to_string(), "message1".to_string()),
("channel2".to_string(), "message2".to_string()),
]
})
);
}
}