From 4fada75fe90558a2561af142ef1bd26486b624b6 Mon Sep 17 00:00:00 2001 From: h7x4 Date: Sat, 30 Nov 2024 03:28:22 +0100 Subject: [PATCH] commands: handle some more response parsing --- src/commands.rs | 23 +++++-- src/commands/client_to_client/channels.rs | 55 ++++++++++++---- src/commands/client_to_client/readmessages.rs | 65 ++++++++++++++++++- 3 files changed, 123 insertions(+), 20 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index 37db815..a9e26f5 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -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>; fn parse_raw_response(raw: &str) -> Result> { - // 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, &'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> for HashMap<&'a str, GenericResponseValue< } } +impl<'a> From> for Vec<(&'a str, GenericResponseValue<'a>)> { + fn from(val: ResponseAttributes<'a>) -> Self { + val.0 + } +} + +impl<'a> From)>> for ResponseAttributes<'a> { + fn from(val: Vec<(&'a str, GenericResponseValue<'a>)>) -> Self { + Self(val) + } +} + /*******************/ /* Parsing Helpers */ /*******************/ diff --git a/src/commands/client_to_client/channels.rs b/src/commands/client_to_client/channels.rs index 5db9c4d..c0ebfbb 100644 --- a/src/commands/client_to_client/channels.rs +++ b/src/commands/client_to_client/channels.rs @@ -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 { - 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::>>() - // .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()] + } + ); } } diff --git a/src/commands/client_to_client/readmessages.rs b/src/commands/client_to_client/readmessages.rs index 7e635a4..348a56c 100644 --- a/src/commands/client_to_client/readmessages.rs +++ b/src/commands/client_to_client/readmessages.rs @@ -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 { - 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()), + ] + }) + ); + } +} \ No newline at end of file