response_tokenizer: rewrite
This commit contains a rewrite of the response tokenizer, which introduces lazy parsing of the response, handling of binary data, some tests, as well as just generally more robustness against errors.
This commit is contained in:
@@ -70,7 +70,7 @@ pub trait Command {
|
||||
fn parse_response(parts: ResponseAttributes<'_>) -> ResponseParserResult<'_, Self::Response>;
|
||||
|
||||
fn parse_raw_response(raw: &str) -> ResponseParserResult<'_, Self::Response> {
|
||||
Self::parse_response(ResponseAttributes::new(raw)?)
|
||||
Self::parse_response(ResponseAttributes::new(raw))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +102,9 @@ pub enum ResponseParserError<'a> {
|
||||
/// A property was found in the response that was not expected.
|
||||
UnexpectedProperty(&'a str),
|
||||
|
||||
/// A property was found multiple times in the response, but was only expected once.
|
||||
DuplicateProperty(&'a str),
|
||||
|
||||
/// The property value is parsable, but the value is invalid or nonsensical.
|
||||
InvalidProperty(&'a str, &'a str),
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ impl Command for Outputs {
|
||||
let mut enabled: Option<bool> = None;
|
||||
let mut attributes: HashMap<String, String> = HashMap::new();
|
||||
|
||||
for (k, v) in Vec::from(parts).into_iter() {
|
||||
for (k, v) in parts.into_vec()?.into_iter() {
|
||||
match k {
|
||||
"outputid" => {
|
||||
// Reset and store the previous output if all fields are present
|
||||
|
||||
@@ -32,7 +32,7 @@ impl Command for Channels {
|
||||
fn parse_response(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<Self::Response, ResponseParserError<'_>> {
|
||||
let parts: Vec<_> = parts.into();
|
||||
let parts: Vec<_> = parts.into_vec()?;
|
||||
let mut channel_names = Vec::with_capacity(parts.len());
|
||||
for (key, value) in parts {
|
||||
debug_assert!(key == "channels");
|
||||
|
||||
@@ -35,7 +35,7 @@ impl Command for ReadMessages {
|
||||
fn parse_response(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<Self::Response, ResponseParserError<'_>> {
|
||||
let parts: Vec<_> = parts.into();
|
||||
let parts: Vec<_> = parts.into_vec()?;
|
||||
debug_assert!(parts.len() % 2 == 0);
|
||||
|
||||
let mut messages = Vec::with_capacity(parts.len() / 2);
|
||||
|
||||
@@ -26,7 +26,7 @@ impl Command for Protocol {
|
||||
fn parse_response(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<Self::Response, ResponseParserError<'_>> {
|
||||
let parts_: Vec<_> = parts.into();
|
||||
let parts_: Vec<_> = parts.into_vec()?;
|
||||
if let Some((k, _)) = parts_.iter().find(|(k, _)| *k != "feature") {
|
||||
return Err(ResponseParserError::UnexpectedProperty(k));
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ impl Command for ProtocolAvailable {
|
||||
fn parse_response(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<Self::Response, ResponseParserError<'_>> {
|
||||
let parts_: Vec<_> = parts.into();
|
||||
let parts_: Vec<_> = parts.into_vec()?;
|
||||
if let Some((k, _)) = parts_.iter().find(|(k, _)| *k != "feature") {
|
||||
return Err(ResponseParserError::UnexpectedProperty(k));
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ impl Command for TagTypes {
|
||||
fn parse_response(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<Self::Response, ResponseParserError<'_>> {
|
||||
let parts: Vec<_> = parts.into();
|
||||
let parts: Vec<_> = parts.into_vec()?;
|
||||
|
||||
let mut tagtypes = Vec::with_capacity(parts.len());
|
||||
for (key, value) in parts.into_iter() {
|
||||
|
||||
@@ -26,7 +26,7 @@ impl Command for TagTypesAvailable {
|
||||
fn parse_response(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<Self::Response, ResponseParserError<'_>> {
|
||||
let parts: Vec<_> = parts.into();
|
||||
let parts: Vec<_> = parts.into_vec()?;
|
||||
|
||||
let mut tagtypes = Vec::with_capacity(parts.len());
|
||||
for (key, value) in parts.into_iter() {
|
||||
|
||||
@@ -24,7 +24,7 @@ impl Command for ListMounts {
|
||||
fn parse_response(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<Self::Response, ResponseParserError<'_>> {
|
||||
let parts: Vec<_> = parts.into();
|
||||
let parts: Vec<_> = parts.into_vec()?;
|
||||
let mut result = Vec::with_capacity(parts.len());
|
||||
for (key, value) in parts.into_iter() {
|
||||
if key != "mount" {
|
||||
|
||||
@@ -28,7 +28,7 @@ impl Command for ListNeighbors {
|
||||
fn parse_response(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<Self::Response, ResponseParserError<'_>> {
|
||||
let parts: Vec<_> = parts.into();
|
||||
let parts: Vec<_> = parts.into_vec()?;
|
||||
debug_assert!(parts.len() % 2 == 0);
|
||||
|
||||
let mut result = HashMap::with_capacity(parts.len() / 2);
|
||||
|
||||
@@ -53,7 +53,7 @@ impl Command for AlbumArt {
|
||||
fn parse_response(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<Self::Response, ResponseParserError<'_>> {
|
||||
let parts: HashMap<_, _> = parts.into();
|
||||
let parts: HashMap<_, _> = parts.into_map()?;
|
||||
|
||||
let size = get_and_parse_property!(parts, "size", Text);
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ impl Command for Count {
|
||||
fn parse_response(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<Self::Response, ResponseParserError<'_>> {
|
||||
let parts: HashMap<_, _> = parts.into();
|
||||
let parts: HashMap<_, _> = parts.into_map()?;
|
||||
|
||||
let songs = get_and_parse_property!(parts, "songs", Text);
|
||||
let playtime = get_and_parse_property!(parts, "playtime", Text);
|
||||
|
||||
@@ -39,7 +39,7 @@ impl Command for GetFingerprint {
|
||||
fn parse_response(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<Self::Response, ResponseParserError<'_>> {
|
||||
let parts: HashMap<_, _> = parts.into();
|
||||
let parts: HashMap<_, _> = parts.into_map()?;
|
||||
|
||||
let chromaprint = get_and_parse_property!(parts, "chromaprint", Text);
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ impl Command for List {
|
||||
fn parse_response(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<Self::Response, ResponseParserError<'_>> {
|
||||
let parts_: Vec<_> = parts.into();
|
||||
let parts_: Vec<_> = parts.into_vec()?;
|
||||
debug_assert!({
|
||||
let key = parts_.first().map(|(k, _)| k);
|
||||
parts_.iter().all(|(k, _)| k == key.unwrap())
|
||||
|
||||
@@ -46,7 +46,7 @@ impl Command for LsInfo {
|
||||
fn parse_response(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<Self::Response, ResponseParserError<'_>> {
|
||||
let parts: Vec<_> = parts.into();
|
||||
let parts: Vec<_> = parts.into_vec()?;
|
||||
let mut playlists = Vec::new();
|
||||
|
||||
for (key, value) in parts {
|
||||
|
||||
@@ -34,7 +34,7 @@ impl Command for ReadComments {
|
||||
fn parse_response(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<Self::Response, ResponseParserError<'_>> {
|
||||
let parts: HashMap<_, _> = parts.into();
|
||||
let parts: HashMap<_, _> = parts.into_map()?;
|
||||
|
||||
let comments = parts
|
||||
.iter()
|
||||
|
||||
@@ -56,7 +56,7 @@ impl Command for ReadPicture {
|
||||
fn parse_response(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<Self::Response, ResponseParserError<'_>> {
|
||||
let parts: HashMap<_, _> = parts.into();
|
||||
let parts: HashMap<_, _> = parts.into_map()?;
|
||||
|
||||
if parts.is_empty() {
|
||||
return Ok(None);
|
||||
|
||||
@@ -39,7 +39,7 @@ impl Command for Rescan {
|
||||
fn parse_response(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<Self::Response, ResponseParserError<'_>> {
|
||||
let parts: HashMap<_, _> = parts.into();
|
||||
let parts: HashMap<_, _> = parts.into_map()?;
|
||||
|
||||
let updating_db = get_and_parse_property!(parts, "updating_db", Text);
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ impl Command for SearchCount {
|
||||
fn parse_response(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<Self::Response, ResponseParserError<'_>> {
|
||||
let parts: HashMap<_, _> = parts.into();
|
||||
let parts: HashMap<_, _> = parts.into_map()?;
|
||||
|
||||
let songs = get_and_parse_property!(parts, "songs", Text);
|
||||
let playtime = get_and_parse_property!(parts, "playtime", Text);
|
||||
|
||||
@@ -39,7 +39,7 @@ impl Command for Update {
|
||||
fn parse_response(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<Self::Response, ResponseParserError<'_>> {
|
||||
let parts: HashMap<_, _> = parts.into();
|
||||
let parts: HashMap<_, _> = parts.into_map()?;
|
||||
|
||||
let updating_db = get_and_parse_property!(parts, "updating_db", Text);
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ impl Command for ListPartitions {
|
||||
fn parse_response(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<Self::Response, ResponseParserError<'_>> {
|
||||
let parts: Vec<_> = parts.into();
|
||||
let parts: Vec<_> = parts.into_vec()?;
|
||||
|
||||
let mut partitions = Vec::with_capacity(parts.len());
|
||||
for (key, value) in parts.into_iter() {
|
||||
|
||||
@@ -26,7 +26,7 @@ impl Command for GetVol {
|
||||
fn parse_response(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<Self::Response, ResponseParserError<'_>> {
|
||||
let parts: HashMap<_, _> = parts.into();
|
||||
let parts: HashMap<_, _> = parts.into_map()?;
|
||||
assert_eq!(parts.len(), 1);
|
||||
let volume = get_and_parse_property!(parts, "volume", Text);
|
||||
Ok(volume)
|
||||
|
||||
@@ -33,7 +33,7 @@ impl Command for ReplayGainStatus {
|
||||
fn parse_response(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<Self::Response, ResponseParserError<'_>> {
|
||||
let parts: HashMap<_, _> = parts.into();
|
||||
let parts: HashMap<_, _> = parts.into_map()?;
|
||||
let replay_gain_mode = get_property!(parts, "replay_gain_mode", Text);
|
||||
|
||||
Ok(ReplayGainStatusResponse {
|
||||
|
||||
@@ -41,7 +41,7 @@ impl Command for Stats {
|
||||
fn parse_response(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<Self::Response, ResponseParserError<'_>> {
|
||||
let parts: HashMap<_, _> = parts.into();
|
||||
let parts: HashMap<_, _> = parts.into_map()?;
|
||||
|
||||
let uptime = get_and_parse_property!(parts, "uptime", Text);
|
||||
let playtime = get_and_parse_property!(parts, "playtime", Text);
|
||||
|
||||
@@ -66,7 +66,7 @@ pub struct StatusResponse {
|
||||
fn parse_status_response(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<StatusResponse, ResponseParserError<'_>> {
|
||||
let parts: HashMap<&str, GenericResponseValue> = parts.into();
|
||||
let parts: HashMap<_, _> = parts.into_map()?;
|
||||
let partition = get_property!(parts, "partition", Text).to_string();
|
||||
|
||||
let volume = match get_property!(parts, "volume", Text) {
|
||||
|
||||
@@ -15,6 +15,12 @@ pub struct AddRequest {
|
||||
position: Option<SongPosition>,
|
||||
}
|
||||
|
||||
impl AddRequest {
|
||||
pub fn new(uri: Uri, position: Option<SongPosition>) -> Self {
|
||||
Self { uri, position }
|
||||
}
|
||||
}
|
||||
|
||||
impl Command for Add {
|
||||
type Request = AddRequest;
|
||||
type Response = ();
|
||||
|
||||
@@ -25,7 +25,7 @@ impl Command for Commands {
|
||||
fn parse_response(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<Self::Response, ResponseParserError<'_>> {
|
||||
let parts: Vec<_> = parts.into();
|
||||
let parts: Vec<_> = parts.into_vec()?;
|
||||
let mut result = Vec::with_capacity(parts.len());
|
||||
for (key, value) in parts.into_iter() {
|
||||
if key != "command" {
|
||||
|
||||
@@ -34,7 +34,7 @@ impl Command for Config {
|
||||
fn parse_response(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<Self::Response, ResponseParserError<'_>> {
|
||||
let parts: HashMap<_, _> = parts.into();
|
||||
let parts: HashMap<_, _> = parts.into_map()?;
|
||||
|
||||
let music_directory = get_property!(parts, "music_directory", Text).to_string();
|
||||
let playlist_directory = get_property!(parts, "playlist_directory", Text).to_string();
|
||||
|
||||
@@ -36,7 +36,7 @@ impl Command for Decoders {
|
||||
) -> Result<Self::Response, ResponseParserError<'_>> {
|
||||
let mut result = Vec::new();
|
||||
let mut current_decoder: Option<Decoder> = None;
|
||||
for (key, value) in Vec::from(parts).into_iter() {
|
||||
for (key, value) in parts.into_vec()?.into_iter() {
|
||||
match key {
|
||||
"plugin" => {
|
||||
if let Some(decoder) = current_decoder.take() {
|
||||
|
||||
@@ -25,7 +25,7 @@ impl Command for NotCommands {
|
||||
fn parse_response(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<Self::Response, ResponseParserError<'_>> {
|
||||
let parts: Vec<_> = parts.into();
|
||||
let parts: Vec<_> = parts.into_vec()?;
|
||||
let mut result = Vec::with_capacity(parts.len());
|
||||
for (key, value) in parts.into_iter() {
|
||||
if key != "command" {
|
||||
|
||||
@@ -25,7 +25,7 @@ impl Command for UrlHandlers {
|
||||
fn parse_response(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<Self::Response, ResponseParserError<'_>> {
|
||||
let parts: Vec<_> = parts.into();
|
||||
let parts: Vec<_> = parts.into_vec()?;
|
||||
let mut url_handlers = Vec::with_capacity(parts.len());
|
||||
for (key, value) in parts.into_iter() {
|
||||
if key != "handler" {
|
||||
|
||||
@@ -100,7 +100,7 @@ impl Command for StickerFind {
|
||||
fn parse_response(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<Self::Response, ResponseParserError<'_>> {
|
||||
let parts: Vec<_> = parts.into();
|
||||
let parts: Vec<_> = parts.into_vec()?;
|
||||
let mut stickers = Vec::with_capacity(parts.len() / 2);
|
||||
|
||||
for sticker_uri_pair in parts.chunks_exact(2) {
|
||||
|
||||
@@ -47,7 +47,7 @@ impl Command for StickerList {
|
||||
fn parse_response(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<Self::Response, ResponseParserError<'_>> {
|
||||
let parts: Vec<_> = parts.into();
|
||||
let parts: Vec<_> = parts.into_vec()?;
|
||||
if let Some((k, _)) = parts.iter().find(|(k, _)| *k != "sticker") {
|
||||
return Err(ResponseParserError::UnexpectedProperty(k));
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ impl Command for StickerNames {
|
||||
fn parse_response(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<Self::Response, ResponseParserError<'_>> {
|
||||
let parts_: Vec<_> = parts.into();
|
||||
let parts_: Vec<_> = parts.into_vec()?;
|
||||
if let Some((k, _)) = parts_.iter().find(|(k, _)| *k != "name") {
|
||||
return Err(ResponseParserError::UnexpectedProperty(k));
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ impl Command for StickerNamesTypes {
|
||||
fn parse_response(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<Self::Response, ResponseParserError<'_>> {
|
||||
let parts: Vec<_> = parts.into();
|
||||
let parts: Vec<_> = parts.into_vec()?;
|
||||
let mut result = HashMap::new();
|
||||
|
||||
for name_type_pair in parts.chunks_exact(2) {
|
||||
|
||||
@@ -26,7 +26,7 @@ impl Command for StickerTypes {
|
||||
fn parse_response(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<Self::Response, ResponseParserError<'_>> {
|
||||
let parts_: Vec<_> = parts.into();
|
||||
let parts_: Vec<_> = parts.into_vec()?;
|
||||
if let Some((k, _)) = parts_.iter().find(|(k, _)| *k != "stickertype") {
|
||||
return Err(ResponseParserError::UnexpectedProperty(k));
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ impl Command for ListPlaylist {
|
||||
fn parse_response(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<Self::Response, ResponseParserError<'_>> {
|
||||
let parts: Vec<_> = parts.into();
|
||||
let parts: Vec<_> = parts.into_vec()?;
|
||||
let mut files = Vec::with_capacity(parts.len());
|
||||
for (key, value) in parts.into_iter() {
|
||||
if key != "file" {
|
||||
|
||||
@@ -40,7 +40,7 @@ impl Command for PlaylistLength {
|
||||
fn parse_response(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<Self::Response, ResponseParserError<'_>> {
|
||||
let parts: HashMap<_, _> = parts.into();
|
||||
let parts: HashMap<_, _> = parts.into_map()?;
|
||||
|
||||
let songs = get_and_parse_property!(parts, "songs", Text);
|
||||
let playtime = get_and_parse_property!(parts, "playtime", Text);
|
||||
|
||||
@@ -9,38 +9,56 @@ pub enum GenericResponseValue<'a> {
|
||||
// Many(Vec<GenericResponseValue<'a>>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ResponseAttributes<'a>(Vec<(&'a str, GenericResponseValue<'a>)>);
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ResponseAttributes<'a> {
|
||||
bytestring: &'a [u8],
|
||||
cursor: usize,
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
pub fn new(raw: &'a str) -> Self {
|
||||
Self::new_from_bytes(raw.as_bytes())
|
||||
}
|
||||
|
||||
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)));
|
||||
pub fn new_from_bytes(bytes: &'a [u8]) -> Self {
|
||||
Self {
|
||||
bytestring: bytes,
|
||||
cursor: 0,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(parts.into())
|
||||
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<HashMap<&'a str, GenericResponseValue<'a>>, ResponseParserError<'a>> {
|
||||
self.into()
|
||||
}
|
||||
|
||||
pub fn into_vec(
|
||||
self,
|
||||
) -> Result<Vec<(&'a str, GenericResponseValue<'a>)>, ResponseParserError<'a>> {
|
||||
self.into()
|
||||
}
|
||||
|
||||
pub fn into_lazy_vec(
|
||||
self,
|
||||
) -> Vec<Result<(&'a str, GenericResponseValue<'a>), ResponseParserError<'a>>> {
|
||||
self.into()
|
||||
}
|
||||
|
||||
pub fn verify_all_keys_equal(&self, expected_key: &str) -> Result<(), ResponseParserError<'a>> {
|
||||
let mut copy = self.clone();
|
||||
copy.cursor = 0;
|
||||
for item in copy {
|
||||
let (key, _) = item?;
|
||||
if key != expected_key {
|
||||
return Err(ResponseParserError::UnexpectedProperty(key));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// pub fn get<'a>(&self, key: &str) -> Option<&GenericResponseValue<'a>> {
|
||||
@@ -48,38 +66,138 @@ impl<'a> ResponseAttributes<'a> {
|
||||
// }
|
||||
}
|
||||
|
||||
impl ResponseAttributes<'_> {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
impl<'a> Iterator for ResponseAttributes<'a> {
|
||||
type Item = Result<(&'a str, GenericResponseValue<'a>), ResponseParserError<'a>>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
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];
|
||||
|
||||
// Skip empty lines
|
||||
if line.is_empty() {
|
||||
self.cursor += newline_pos + 1;
|
||||
return self.next();
|
||||
}
|
||||
|
||||
// NOTE: it is important that this happens before any None returns,
|
||||
// so that the iterator advances despite errors.
|
||||
self.cursor += newline_pos + 1;
|
||||
|
||||
let mut keyval = line.splitn(2, |&b| b == b':');
|
||||
let key_bytes = keyval.next()?;
|
||||
|
||||
// TODO: should this be a proper runtime error?
|
||||
debug_assert!(!key_bytes.is_empty());
|
||||
debug_assert!(std::str::from_utf8(key_bytes).is_ok());
|
||||
let key = std::str::from_utf8(key_bytes).ok()?;
|
||||
|
||||
// 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.
|
||||
if key == "binary" {
|
||||
let byte_count = match keyval.next() {
|
||||
Some(count) => count.trim_ascii_start(),
|
||||
None => {
|
||||
// TODO: throw more specific error
|
||||
return Some(Err(ResponseParserError::UnexpectedEOF));
|
||||
}
|
||||
};
|
||||
let byte_count_str = match std::str::from_utf8(byte_count) {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
// TODO: throw more specific error
|
||||
return Some(Err(ResponseParserError::SyntaxError(
|
||||
0,
|
||||
"Invalid byte count",
|
||||
)));
|
||||
}
|
||||
};
|
||||
let byte_count: usize = match byte_count_str.parse() {
|
||||
Ok(n) => n,
|
||||
Err(_) => {
|
||||
// TODO: throw more specific error
|
||||
return Some(Err(ResponseParserError::SyntaxError(
|
||||
0,
|
||||
"Invalid byte count",
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
let value_start = self.cursor;
|
||||
let value_end = self.cursor + byte_count;
|
||||
if value_end > self.bytestring.len() {
|
||||
return Some(Err(ResponseParserError::UnexpectedEOF));
|
||||
}
|
||||
let value_bytes = &self.bytestring[value_start..value_end];
|
||||
|
||||
debug_assert!(
|
||||
self.bytestring[value_end..]
|
||||
.iter()
|
||||
.next()
|
||||
.is_none_or(|&b| b == b'\n')
|
||||
);
|
||||
|
||||
// Skip the binary data and the following newline
|
||||
self.cursor = value_end + 1;
|
||||
|
||||
Some(Ok((key, GenericResponseValue::Binary(value_bytes))))
|
||||
} else {
|
||||
let value_bytes = match keyval.next() {
|
||||
Some(v) => v.trim_ascii_start(),
|
||||
None => b"",
|
||||
};
|
||||
// TODO: this should be a proper runtime error, the specification
|
||||
// declares that all string values are UTF-8.
|
||||
debug_assert!(std::str::from_utf8(value_bytes).is_ok());
|
||||
let value_str = std::str::from_utf8(value_bytes).ok()?;
|
||||
|
||||
Some(Ok((key, GenericResponseValue::Text(value_str))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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>> {
|
||||
impl<'a> From<ResponseAttributes<'a>>
|
||||
for Result<HashMap<&'a str, GenericResponseValue<'a>>, ResponseParserError<'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()
|
||||
let mut map = HashMap::new();
|
||||
for item in val {
|
||||
let (k, v) = item?;
|
||||
if map.contains_key(k) {
|
||||
return Err(ResponseParserError::DuplicateProperty(k));
|
||||
}
|
||||
map.insert(k, v);
|
||||
}
|
||||
Ok(map)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<ResponseAttributes<'a>> for Vec<(&'a str, GenericResponseValue<'a>)> {
|
||||
impl<'a> From<ResponseAttributes<'a>>
|
||||
for Vec<Result<(&'a str, GenericResponseValue<'a>), ResponseParserError<'a>>>
|
||||
{
|
||||
fn from(val: ResponseAttributes<'a>) -> Self {
|
||||
val.0
|
||||
val.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Vec<(&'a str, GenericResponseValue<'a>)>> for ResponseAttributes<'a> {
|
||||
fn from(val: Vec<(&'a str, GenericResponseValue<'a>)>) -> Self {
|
||||
Self(val)
|
||||
impl<'a> From<ResponseAttributes<'a>>
|
||||
for Result<Vec<(&'a str, GenericResponseValue<'a>)>, ResponseParserError<'a>>
|
||||
{
|
||||
fn from(val: ResponseAttributes<'a>) -> Self {
|
||||
val.collect()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,11 +323,12 @@ macro_rules! get_next_optional_property {
|
||||
macro_rules! get_next_property {
|
||||
($parts:ident, $variant:ident) => {
|
||||
match $parts.next() {
|
||||
Some((name, value)) => (
|
||||
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),
|
||||
}
|
||||
};
|
||||
@@ -239,7 +358,7 @@ macro_rules! get_next_and_parse_optional_property {
|
||||
macro_rules! get_next_and_parse_property {
|
||||
($parts:ident, $variant:ident) => {
|
||||
match $parts.next() {
|
||||
Some((name, value)) => {
|
||||
Some(Ok((name, value))) => {
|
||||
let prop = crate::response_tokenizer::_expect_property_type!(
|
||||
{ Some(value) },
|
||||
name,
|
||||
@@ -251,12 +370,13 @@ macro_rules! get_next_and_parse_property {
|
||||
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, HashSet};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub(crate) use _expect_property_type;
|
||||
pub(crate) use _parse_optional_property_type;
|
||||
@@ -276,36 +396,165 @@ use crate::commands::ResponseParserError;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use indoc::indoc;
|
||||
|
||||
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();
|
||||
fn test_valid_hashmap_uniqueness_assert() -> Result<(), ResponseParserError<'static>> {
|
||||
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 invalid_maplike_attrs: ResponseAttributes = vec![
|
||||
("a", GenericResponseValue::Text("1")),
|
||||
("b", GenericResponseValue::Text("2")),
|
||||
("c", GenericResponseValue::Text("3")),
|
||||
("a", GenericResponseValue::Text("4")),
|
||||
]
|
||||
.into();
|
||||
let raw_response = indoc! {
|
||||
"a: 1
|
||||
b: 2
|
||||
c: 3
|
||||
a: 4
|
||||
OK"
|
||||
};
|
||||
ResponseAttributes::new(raw_response).into_map().unwrap();
|
||||
}
|
||||
|
||||
let map: HashMap<_, _> = invalid_maplike_attrs.into();
|
||||
assert_eq!(map.len(), 4);
|
||||
#[test]
|
||||
fn test_response_attributes_single_attribute() -> Result<(), ResponseParserError<'static>> {
|
||||
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<'static>> {
|
||||
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<'static>> {
|
||||
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<'static>> {
|
||||
let raw_response = indoc! {
|
||||
"name: Sticker1
|
||||
type: emoji"
|
||||
};
|
||||
let attrs = ResponseAttributes::new(raw_response);
|
||||
let vec: Result<Vec<_>, ResponseParserError> = attrs.into_vec();
|
||||
assert!(matches!(vec, Err(ResponseParserError::UnexpectedEOF)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_response_attributes_repeated_attribute() -> Result<(), ResponseParserError<'static>> {
|
||||
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<'static>> {
|
||||
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<'static>> {
|
||||
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<'static>> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user