//! Tokenizer for Mpd responses named [`ResponseAttributes`]. //! //! This module also contains some helper macros that makes it easier to //! create response parsers. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum GenericResponseValue<'a> { Text(&'a str), Binary(&'a [u8]), // Many(Vec>), } #[derive(Debug, Clone)] pub struct ResponseAttributes<'a> { bytestring: &'a [u8], cursor: usize, } impl<'a> ResponseAttributes<'a> { pub fn new(raw: &'a str) -> Self { Self::new_from_bytes(raw.as_bytes()) } pub fn new_from_bytes(bytes: &'a [u8]) -> Self { Self { bytestring: bytes, cursor: 0, } } pub fn is_empty(&self) -> bool { self.cursor >= self.bytestring.len() || self.bytestring[self.cursor..].starts_with(b"OK") } pub fn into_map( self, ) -> Result>, ResponseParserError> { self.into() } pub fn into_vec(self) -> Result)>, ResponseParserError> { self.into() } pub fn into_lazy_vec( self, ) -> Vec), ResponseParserError>> { self.into() } pub fn verify_all_keys_equal(&self, expected_key: &str) -> Result<(), ResponseParserError> { let mut copy = self.clone(); copy.cursor = 0; for item in copy { let (key, _) = item?; if key != expected_key { return Err(ResponseParserError::UnexpectedProperty(key.to_string())); } } Ok(()) } // 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<'a> Iterator for ResponseAttributes<'a> { type Item = Result<(&'a str, GenericResponseValue<'a>), ResponseParserError>; fn next(&mut self) -> Option { loop { if self.cursor >= self.bytestring.len() { return Some(Err(ResponseParserError::UnexpectedEOF)); } if self.bytestring[self.cursor..].starts_with(b"OK") { return None; } let remaining = &self.bytestring[self.cursor..]; let newline_pos = remaining .iter() .position(|&b| b == b'\n') .unwrap_or(remaining.len()); let line = &remaining[..newline_pos]; // NOTE: it is important that this happens before any further None returns, // so that the iterator advances despite errors. self.cursor += newline_pos + 1; if line.is_empty() { continue; } return Some(parse_line(line, self)); } } } fn parse_line<'a>( line: &'a [u8], state: &mut ResponseAttributes<'a>, ) -> Result<(&'a str, GenericResponseValue<'a>), ResponseParserError> { let mut parts = line.splitn(2, |&b| b == b':'); let key = parts .next() .filter(|k| !k.is_empty()) .and_then(|k| std::str::from_utf8(k).ok()) .ok_or_else(|| ResponseParserError::SyntaxError(0, "Invalid key".into()))?; match key { "binary" => parse_binary(parts.next(), state).map(|v| (key, v)), _ => { let value = parts.next().unwrap_or(b"").trim_ascii_start(); let text = std::str::from_utf8(value) .map_err(|_| ResponseParserError::SyntaxError(0, "Invalid UTF-8".into()))?; Ok((key, GenericResponseValue::Text(text))) } } } fn parse_binary<'a>( count_bytes: Option<&[u8]>, state: &mut ResponseAttributes<'a>, ) -> Result, ResponseParserError> { // In the case of binary data, the following value will be the byte count // in decimal, and the actual binary data will follow in the next N bytes, // followed by a newline. // // We parse the number and assign the binary data to the "binary" key. let count = count_bytes .map(|b| b.trim_ascii_start()) .and_then(|b| std::str::from_utf8(b).ok()) .and_then(|s| s.parse::().ok()) .ok_or_else(|| ResponseParserError::SyntaxError(0, "Invalid byte count".into()))?; let start = state.cursor; let end = start + count; if end > state.bytestring.len() { return Err(ResponseParserError::UnexpectedEOF); } let bytes = &state.bytestring[start..end]; if state.bytestring.get(end).is_some_and(|&b| b != b'\n') { return Err(ResponseParserError::SyntaxError( 0, "Missing newline".into(), )); } state.cursor = end + 1; Ok(GenericResponseValue::Binary(bytes)) } impl<'a> From> for Result>, ResponseParserError> { fn from(val: ResponseAttributes<'a>) -> Self { let mut map = HashMap::new(); for item in val { let (k, v) = item?; if map.contains_key(k) { return Err(ResponseParserError::DuplicateProperty(k.to_string())); } map.insert(k, v); } Ok(map) } } impl<'a> From> for Vec), ResponseParserError>> { fn from(val: ResponseAttributes<'a>) -> Self { val.collect() } } impl<'a> From> for Result)>, ResponseParserError> { fn from(val: ResponseAttributes<'a>) -> Self { val.collect() } } // 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.to_string(), actual_type.to_string(), ), ); } None => None, } }; } macro_rules! _parse_optional_property_type { ($name:expr, $property:expr) => { $property .map(|value| { value.parse().map_err(|_| { crate::commands::ResponseParserError::InvalidProperty( $name.to_string(), value.to_string(), ) }) }) .transpose()? }; } macro_rules! _unwrap_optional_property_type { ($name:expr, $property:expr) => { match $property { Some(value) => value, None => { return Err(crate::commands::ResponseParserError::MissingProperty( $name.to_string(), )) } } }; } 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(Ok((name, value))) => ( name, crate::response_tokenizer::_expect_property_type!({ Some(value) }, name, $variant) .unwrap(), ), Some(Err(e)) => return Err(e), 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(Ok((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), ) } Some(Err(e)) => return Err(e), None => return Err(crate::commands::ResponseParserError::UnexpectedEOF), } }; } use std::collections::HashMap; 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 indoc::indoc; use super::*; #[test] #[cfg(debug_assertions)] fn test_valid_hashmap_uniqueness_assert() -> Result<(), ResponseParserError> { let raw_response = indoc! { "a: 1 A: 2 A : 3 b: 4 OK" }; let attrs = ResponseAttributes::new(raw_response); let map: HashMap<_, _> = attrs.into_map()?; assert_eq!(map.len(), 4); Ok(()) } #[test] #[cfg(debug_assertions)] #[should_panic] fn test_invalid_hashmap_uniqueness_assert() { let raw_response = indoc! { "a: 1 b: 2 c: 3 a: 4 OK" }; ResponseAttributes::new(raw_response).into_map().unwrap(); } #[test] fn test_response_attributes_single_attribute() -> Result<(), ResponseParserError> { let raw_response = indoc! { "name: Sticker1 OK" }; let attrs = ResponseAttributes::new(raw_response); let map: HashMap<_, _> = attrs.into_map()?; assert_eq!(map.len(), 1); assert_eq!( map.get("name"), Some(&GenericResponseValue::Text("Sticker1")) ); Ok(()) } #[test] fn test_response_attributes_multiple_attributes() -> Result<(), ResponseParserError> { let raw_response = indoc! { "name: Sticker1 type: emoji size: 128 OK" }; let attrs = ResponseAttributes::new(raw_response); let map: HashMap<_, _> = attrs.into_map()?; assert_eq!(map.len(), 3); assert_eq!( map.get("name"), Some(&GenericResponseValue::Text("Sticker1")) ); assert_eq!(map.get("type"), Some(&GenericResponseValue::Text("emoji"))); assert_eq!(map.get("size"), Some(&GenericResponseValue::Text("128"))); Ok(()) } #[test] fn test_response_attributes_empty_response() -> Result<(), ResponseParserError> { let raw_response = indoc! { "OK" }; let attrs = ResponseAttributes::new(raw_response); let map: HashMap<_, _> = attrs.into_map()?; assert_eq!(map.len(), 0); Ok(()) } #[test] fn test_response_attributes_unexpected_eof() -> Result<(), ResponseParserError> { let raw_response = indoc! { "name: Sticker1 type: emoji" }; let attrs = ResponseAttributes::new(raw_response); let vec: Result, ResponseParserError> = attrs.into_vec(); assert!(matches!(vec, Err(ResponseParserError::UnexpectedEOF))); Ok(()) } #[test] fn test_response_attributes_repeated_attribute() -> Result<(), ResponseParserError> { let raw_response = indoc! { "name: Sticker1 name: Sticker2 OK" }; let attrs = ResponseAttributes::new(raw_response); let vec = attrs.into_vec()?; assert_eq!(vec.len(), 2); assert_eq!(vec[0], ("name", GenericResponseValue::Text("Sticker1"))); assert_eq!(vec[1], ("name", GenericResponseValue::Text("Sticker2"))); Ok(()) } #[test] fn test_response_attributes_empty_line() -> Result<(), ResponseParserError> { let raw_response = indoc! { "name: Sticker1 type: emoji OK" }; let attrs = ResponseAttributes::new(raw_response); let map: HashMap<_, _> = attrs.into_map()?; assert_eq!(map.len(), 2); assert_eq!( map.get("name"), Some(&GenericResponseValue::Text("Sticker1")) ); assert_eq!(map.get("type"), Some(&GenericResponseValue::Text("emoji"))); Ok(()) } #[test] fn test_response_attributes_no_value() -> Result<(), ResponseParserError> { let raw_response = indoc! { "name: type: emoji OK" }; let attrs = ResponseAttributes::new(raw_response); let map: HashMap<_, _> = attrs.into_map()?; assert_eq!(map.len(), 2); assert_eq!(map.get("name"), Some(&GenericResponseValue::Text(""))); assert_eq!(map.get("type"), Some(&GenericResponseValue::Text("emoji"))); Ok(()) } #[test] fn test_response_attributes_binary_data() -> Result<(), ResponseParserError> { let bytestring: &[u8] = b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09"; let raw_response = { let mut response = format!("binary: {}\n", bytestring.len()).into_bytes(); response.extend_from_slice(bytestring); response.extend_from_slice(b"\nOK"); response }; let attrs = ResponseAttributes::new_from_bytes(&raw_response); let map: HashMap<_, _> = attrs.into_map().unwrap(); assert_eq!(map.len(), 1); assert_eq!( map.get("binary"), Some(&GenericResponseValue::Binary(bytestring)) ); Ok(()) } }