make ResponseParserError self-contained, impl thiserror

This commit is contained in:
2025-12-08 05:31:13 +09:00
parent f470f19c2c
commit 612fa4db11
49 changed files with 212 additions and 173 deletions

View File

@@ -16,6 +16,7 @@ chrono = { version = "0.4.42", features = ["serde"] }
lalrpop-util = { version = "0.22.2", features = ["lexer"] }
paste = "1.0.15"
serde = { version = "1.0.228", features = ["derive"] }
thiserror = "2.0.17"
[dev-dependencies]
indoc = "2.0.7"

View File

@@ -109,10 +109,10 @@ where
/// Parses the response from its tokenized parts.
/// See also [`parse_raw`].
fn parse(parts: ResponseAttributes<'a>) -> Result<Self, ResponseParserError<'a>>;
fn parse(parts: ResponseAttributes<'a>) -> Result<Self, ResponseParserError>;
/// Parses the response from its raw byte representation.
fn parse_raw(raw: &'a [u8]) -> Result<Self, ResponseParserError<'a>> {
fn parse_raw(raw: &'a [u8]) -> Result<Self, ResponseParserError> {
Self::parse(ResponseAttributes::new_from_bytes(raw))
}
}
@@ -154,11 +154,11 @@ pub trait Command<'req, 'res> {
/// Parse the response from its tokenized parts. See also [`parse_raw_response`].
fn parse_response(
parts: ResponseAttributes<'res>,
) -> Result<Self::Response, ResponseParserError<'res>> {
) -> Result<Self::Response, ResponseParserError> {
Self::Response::parse(parts)
}
/// Parse the raw response string into a response.
fn parse_raw_response(raw: &'res [u8]) -> Result<Self::Response, ResponseParserError<'res>> {
fn parse_raw_response(raw: &'res [u8]) -> Result<Self::Response, ResponseParserError> {
Self::Response::parse_raw(raw)
}
}
@@ -224,7 +224,7 @@ macro_rules! empty_command_response {
fn parse(
_parts: crate::commands::ResponseAttributes<'_>,
) -> Result<Self, crate::commands::ResponseParserError<'_>> {
) -> Result<Self, crate::commands::ResponseParserError> {
debug_assert!(_parts.is_empty());
Ok(paste::paste! { [<$name Response>] })
}
@@ -319,8 +319,9 @@ macro_rules! single_optional_item_command_request {
let item = parts
.next()
.map(|s| {
s.parse()
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))
s.parse().map_err(|_| {
crate::commands::RequestParserError::SyntaxError(0, s.to_owned())
})
})
.transpose()?;
@@ -350,13 +351,13 @@ macro_rules! single_item_command_response {
fn parse(
parts: crate::commands::ResponseAttributes<'_>,
) -> Result<Self, crate::commands::ResponseParserError<'_>> {
) -> Result<Self, crate::commands::ResponseParserError> {
let map = parts.into_map()?;
debug_assert!(map.len() == 1, "Expected only one property in response");
let item_token = map.get($item_name).ok_or(
crate::commands::ResponseParserError::MissingProperty($item_name),
crate::commands::ResponseParserError::MissingProperty($item_name.to_string()),
)?;
let item_ = crate::response_tokenizer::expect_property_type!(
Some(item_token),
@@ -364,7 +365,10 @@ macro_rules! single_item_command_response {
Text
);
let item = item_.parse::<$item_type>().map_err(|_| {
crate::commands::ResponseParserError::InvalidProperty($item_name, item_)
crate::commands::ResponseParserError::InvalidProperty(
$item_name.to_string(),
item_.to_string(),
)
})?;
Ok(paste::paste! { [<$name Response>] ( item ) })
@@ -391,12 +395,12 @@ macro_rules! multi_item_command_response {
fn parse(
parts: crate::commands::ResponseAttributes<'_>,
) -> Result<Self, crate::commands::ResponseParserError<'_>> {
) -> Result<Self, crate::commands::ResponseParserError> {
// TODO: use lazy vec
let parts_: Vec<_> = parts.into_vec()?;
if let Some((k, _)) = parts_.iter().find(|(k, _)| *k != $item_name) {
return Err(ResponseParserError::UnexpectedProperty(k));
return Err(ResponseParserError::UnexpectedProperty(k.to_string()));
}
let mut items = Vec::with_capacity(parts_.len());
@@ -406,8 +410,8 @@ macro_rules! multi_item_command_response {
let unwrapped_value = expect_property_type!(Some(value.1), $item_name, Text);
let parsed_value = unwrapped_value.parse::<$item_type>().map_err(|_| {
crate::commands::ResponseParserError::InvalidProperty(
$item_name,
unwrapped_value,
$item_name.to_string(),
unwrapped_value.to_string(),
)
})?;
@@ -426,6 +430,7 @@ pub(crate) use multi_item_command_response;
pub(crate) use single_item_command_request;
pub(crate) use single_item_command_response;
pub(crate) use single_optional_item_command_request;
use thiserror::Error;
#[derive(Debug, Clone, PartialEq)]
pub enum RequestParserError {
@@ -437,32 +442,34 @@ pub enum RequestParserError {
MissingNewline,
}
pub type ResponseParserResult<'a, T> = Result<T, ResponseParserError<'a>>;
pub type ResponseParserResult<'a, T> = Result<T, ResponseParserError>;
// TODO: should these be renamed to fit the mpd docs?
// "Attribute" instead of "Property"?
#[derive(Debug, Clone, PartialEq)]
pub enum ResponseParserError<'a> {
/// A property was expected to be present in the response, but was not found.
MissingProperty(&'a str),
#[derive(Error, Debug, Clone, PartialEq)]
pub enum ResponseParserError {
#[error("A property was expected to be present in the response, but was not found: {0}")]
MissingProperty(String),
// TODO: change name to UnexpectedPropertyEncoding
/// An expected property was found in the response, but its encoding was not as expected. (e.g. binary instead of text)
UnexpectedPropertyType(&'a str, &'a str),
#[error(
"An expected property was found in the response, but its encoding was not as expected: {0}: {1}"
)]
UnexpectedPropertyType(String, String),
/// A property was found in the response that was not expected.
UnexpectedProperty(&'a str),
#[error("A property was found in the response that was not expected: {0}")]
UnexpectedProperty(String),
/// A property was found multiple times in the response, but was only expected once.
DuplicateProperty(&'a str),
#[error("A property was found multiple times in the response, but was only expected once: {0}")]
DuplicateProperty(String),
/// The property value is parsable, but the value is invalid or nonsensical.
InvalidProperty(&'a str, &'a str),
#[error("The property value is parsable, but the value is invalid or nonsensical: {0}: {1}")]
InvalidProperty(String, String),
/// Could not parse the response due to a syntax error.
SyntaxError(u64, &'a str),
#[error("Could not parse the response due to a syntax error at position {0}: {1}")]
SyntaxError(u64, String),
/// Response ended early, while more properties were expected.
#[error("Response ended early, while more properties were expected")]
UnexpectedEOF,
// MissingNewline,
}

View File

@@ -33,7 +33,7 @@ impl CommandResponse<'_> for OutputsResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let mut outputs = Vec::new();
let mut id: Option<AudioOutputId> = None;
@@ -61,7 +61,7 @@ impl CommandResponse<'_> for OutputsResponse {
let id_s = expect_property_type!(Some(v), k, Text);
id = Some(
id_s.parse()
.map_err(|_| ResponseParserError::SyntaxError(0, id_s))?,
.map_err(|_| ResponseParserError::SyntaxError(0, id_s.to_string()))?,
);
}
"outputname" => {
@@ -74,7 +74,7 @@ impl CommandResponse<'_> for OutputsResponse {
let val_s = expect_property_type!(Some(v), k, Text);
let val: u64 = val_s
.parse::<u64>()
.map_err(|_| ResponseParserError::SyntaxError(0, val_s))?;
.map_err(|_| ResponseParserError::SyntaxError(0, val_s.to_string()))?;
enabled = Some(val != 0);
}
"attribute" => {
@@ -82,16 +82,16 @@ impl CommandResponse<'_> for OutputsResponse {
let mut parts = value.splitn(2, '=');
let attr_key = parts
.next()
.ok_or(ResponseParserError::SyntaxError(0, value))?
.ok_or(ResponseParserError::SyntaxError(0, value.to_string()))?
.to_string();
let attr_value = parts
.next()
.ok_or(ResponseParserError::SyntaxError(0, value))?
.ok_or(ResponseParserError::SyntaxError(0, value.to_string()))?
.to_string();
attributes.insert(attr_key, attr_value);
}
_ => {
return Err(ResponseParserError::UnexpectedProperty(k));
return Err(ResponseParserError::UnexpectedProperty(k.to_string()));
}
}
}

View File

@@ -24,7 +24,7 @@ impl<'a> CommandResponse<'a> for ChannelsResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: Vec<_> = parts.into_vec()?;
let mut channel_names = Vec::with_capacity(parts.len());
for (key, value) in parts {
@@ -32,7 +32,7 @@ impl<'a> CommandResponse<'a> for ChannelsResponse {
let channel_name = expect_property_type!(Some(value), "channels", Text);
let channel_name = channel_name
.parse()
.map_err(|_| ResponseParserError::SyntaxError(0, channel_name))?;
.map_err(|_| ResponseParserError::SyntaxError(0, channel_name.to_string()))?;
channel_names.push(channel_name);
}

View File

@@ -28,7 +28,7 @@ impl CommandResponse<'_> for ReadMessagesResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: Vec<_> = parts.into_vec()?;
debug_assert!(parts.len() % 2 == 0);
@@ -44,7 +44,7 @@ impl CommandResponse<'_> for ReadMessagesResponse {
let channel = expect_property_type!(Some(cvalue), "channel", Text);
let channel = channel
.parse()
.map_err(|_| ResponseParserError::SyntaxError(0, channel))?;
.map_err(|_| ResponseParserError::SyntaxError(0, channel.to_string()))?;
let message = expect_property_type!(Some(mvalue), "message", Text).to_string();

View File

@@ -1,11 +1,11 @@
use crate::{
commands::{Command, empty_command_response, single_item_command_request},
commands::{Command, empty_command_response, single_optional_item_command_request},
types::SongPosition,
};
pub struct Play;
single_item_command_request!(Play, "play", SongPosition);
single_optional_item_command_request!(Play, "play", SongPosition);
empty_command_response!(Play);

View File

@@ -1,11 +1,11 @@
use crate::{
commands::{Command, empty_command_response, single_item_command_request},
commands::{Command, empty_command_response, single_optional_item_command_request},
types::SongId,
};
pub struct PlayId;
single_item_command_request!(PlayId, "playid", SongId);
single_optional_item_command_request!(PlayId, "playid", SongId);
empty_command_response!(PlayId);

View File

@@ -23,7 +23,7 @@ impl CommandResponse<'_> for ListNeighborsResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: Vec<_> = parts.into_vec()?;
debug_assert!(parts.len() % 2 == 0);

View File

@@ -72,7 +72,7 @@ impl CommandResponse<'_> for AlbumArtResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
let size = get_and_parse_property!(parts, "size", Text);

View File

@@ -81,7 +81,7 @@ impl CommandResponse<'_> for CountResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
let songs = get_and_parse_property!(parts, "songs", Text);

View File

@@ -96,19 +96,19 @@ impl CommandResponse<'_> for FindResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
DbSelectionPrintResponse::parse(parts)?
.into_iter()
.map(|i| match i {
DbSelectionPrintResponse::Song(db_song_info) => Ok(db_song_info),
DbSelectionPrintResponse::Directory(_db_directory_info) => {
Err(ResponseParserError::UnexpectedProperty("directory"))
}
DbSelectionPrintResponse::Playlist(_db_playlist_info) => {
Err(ResponseParserError::UnexpectedProperty("playlist"))
}
DbSelectionPrintResponse::Directory(_db_directory_info) => Err(
ResponseParserError::UnexpectedProperty("directory".to_string()),
),
DbSelectionPrintResponse::Playlist(_db_playlist_info) => Err(
ResponseParserError::UnexpectedProperty("playlist".to_string()),
),
})
.collect::<Result<Vec<DbSongInfo>, ResponseParserError<'_>>>()
.collect::<Result<Vec<DbSongInfo>, ResponseParserError>>()
.map(FindResponse)
}
}

View File

@@ -26,7 +26,7 @@ impl CommandResponse<'_> for GetFingerprintResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
let chromaprint = get_and_parse_property!(parts, "chromaprint", Text);

View File

@@ -117,7 +117,7 @@ impl CommandResponse<'_> for ListResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts_: Vec<_> = parts.into_vec()?;
debug_assert!({
let key = parts_.first().map(|(k, _)| k);

View File

@@ -26,7 +26,7 @@ impl CommandResponse<'_> for ListAllResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let result = DbSelectionPrintResponse::parse(parts)?;
Ok(ListAllResponse(result))

View File

@@ -26,7 +26,7 @@ impl CommandResponse<'_> for ListAllInfoResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let result = DbSelectionPrintResponse::parse(parts)?;
Ok(ListAllInfoResponse(result))

View File

@@ -25,19 +25,19 @@ impl CommandResponse<'_> for ListFilesResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
DbSelectionPrintResponse::parse(parts)?
.into_iter()
.map(|i| match i {
DbSelectionPrintResponse::Directory(db_directory_info) => Ok(db_directory_info),
DbSelectionPrintResponse::Song(_db_song_info) => {
Err(ResponseParserError::UnexpectedProperty("file"))
}
DbSelectionPrintResponse::Playlist(_db_playlist_info) => {
Err(ResponseParserError::UnexpectedProperty("playlist"))
Err(ResponseParserError::UnexpectedProperty("file".to_string()))
}
DbSelectionPrintResponse::Playlist(_db_playlist_info) => Err(
ResponseParserError::UnexpectedProperty("playlist".to_string()),
),
})
.collect::<Result<Vec<_>, ResponseParserError<'_>>>()
.collect::<Result<Vec<_>, ResponseParserError>>()
.map(ListFilesResponse)
}
}

View File

@@ -25,7 +25,7 @@ impl CommandResponse<'_> for LsInfoResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let result = DbSelectionPrintResponse::parse(parts)?;
Ok(LsInfoResponse(result))

View File

@@ -24,14 +24,16 @@ impl CommandResponse<'_> for ReadCommentsResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
let comments = parts
.iter()
.map(|(k, v)| match v {
GenericResponseValue::Text(s) => Ok((k.to_string(), s.to_string())),
GenericResponseValue::Binary(_) => Err(ResponseParserError::SyntaxError(1, k)),
GenericResponseValue::Binary(_) => {
Err(ResponseParserError::SyntaxError(1, k.to_string()))
}
})
.collect::<Result<HashMap<_, _>, ResponseParserError>>()?;

View File

@@ -75,7 +75,7 @@ impl CommandResponse<'_> for ReadPictureResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
// TODO: is empty response possible?

View File

@@ -29,7 +29,7 @@ impl CommandResponse<'_> for RescanResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
let updating_db = get_and_parse_property!(parts, "updating_db", Text);

View File

@@ -96,19 +96,19 @@ impl CommandResponse<'_> for SearchResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
DbSelectionPrintResponse::parse(parts)?
.into_iter()
.map(|i| match i {
DbSelectionPrintResponse::Song(db_song_info) => Ok(db_song_info),
DbSelectionPrintResponse::Directory(_db_directory_info) => {
Err(ResponseParserError::UnexpectedProperty("directory"))
}
DbSelectionPrintResponse::Playlist(_db_playlist_info) => {
Err(ResponseParserError::UnexpectedProperty("playlist"))
}
DbSelectionPrintResponse::Directory(_db_directory_info) => Err(
ResponseParserError::UnexpectedProperty("directory".to_string()),
),
DbSelectionPrintResponse::Playlist(_db_playlist_info) => Err(
ResponseParserError::UnexpectedProperty("playlist".to_string()),
),
})
.collect::<Result<Vec<DbSongInfo>, ResponseParserError<'_>>>()
.collect::<Result<Vec<DbSongInfo>, ResponseParserError>>()
.map(SearchResponse)
}
}

View File

@@ -82,7 +82,7 @@ impl CommandResponse<'_> for SearchCountResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
let songs = get_and_parse_property!(parts, "songs", Text);

View File

@@ -29,7 +29,7 @@ impl CommandResponse<'_> for UpdateResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
let updating_db = get_and_parse_property!(parts, "updating_db", Text);

View File

@@ -26,13 +26,16 @@ impl CommandResponse<'_> for ReplayGainStatusResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
let replay_gain_mode = get_property!(parts, "replay_gain_mode", Text);
Ok(ReplayGainStatusResponse {
replay_gain_mode: ReplayGainModeMode::from_str(replay_gain_mode).map_err(|_| {
ResponseParserError::InvalidProperty("replay_gain_mode", replay_gain_mode)
ResponseParserError::InvalidProperty(
"replay_gain_mode".to_string(),
replay_gain_mode.to_string(),
)
})?,
})
}

View File

@@ -32,7 +32,7 @@ impl CommandResponse<'_> for CurrentSongResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let mut parts: HashMap<_, _> = parts.into_map()?;
let position: SongPosition = get_and_parse_property!(parts, "Pos", Text);

View File

@@ -33,7 +33,7 @@ impl CommandResponse<'_> for StatsResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
let uptime = get_and_parse_property!(parts, "uptime", Text);

View File

@@ -74,29 +74,33 @@ impl CommandResponse<'_> for StatusResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
let partition = get_property!(parts, "partition", Text).to_string();
let volume = match get_property!(parts, "volume", Text) {
"-1" => None,
volume => Some(
volume
.parse()
.map_err(|_| ResponseParserError::InvalidProperty("volume", volume))?,
),
volume => Some(volume.parse().map_err(|_| {
ResponseParserError::InvalidProperty("volume".to_string(), volume.to_string())
})?),
};
let repeat = match get_property!(parts, "repeat", Text) {
"0" => Ok(false),
"1" => Ok(true),
repeat => Err(ResponseParserError::InvalidProperty("repeat", repeat)),
repeat => Err(ResponseParserError::InvalidProperty(
"repeat".to_string(),
repeat.to_string(),
)),
}?;
let random = match get_property!(parts, "random", Text) {
"0" => Ok(false),
"1" => Ok(true),
random => Err(ResponseParserError::InvalidProperty("random", random)),
random => Err(ResponseParserError::InvalidProperty(
"random".to_string(),
random.to_string(),
)),
}?;
let single = get_and_parse_property!(parts, "single", Text);
@@ -116,14 +120,18 @@ impl CommandResponse<'_> for StatusResponse {
let mut parts = time.split(':');
let elapsed = parts
.next()
.ok_or(ResponseParserError::SyntaxError(0, time))?
.ok_or(ResponseParserError::SyntaxError(0, time.to_string()))?
.parse()
.map_err(|_| ResponseParserError::InvalidProperty("time", time))?;
.map_err(|_| {
ResponseParserError::InvalidProperty("time".to_string(), time.to_string())
})?;
let duration = parts
.next()
.ok_or(ResponseParserError::SyntaxError(0, time))?
.ok_or(ResponseParserError::SyntaxError(0, time.to_string()))?
.parse()
.map_err(|_| ResponseParserError::InvalidProperty("time", time))?;
.map_err(|_| {
ResponseParserError::InvalidProperty("time".to_string(), time.to_string())
})?;
Some((elapsed, duration))
}
None => None,

View File

@@ -73,12 +73,12 @@ impl CommandResponse<'_> for AddIdResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: Vec<_> = parts.into();
let mut iter = parts.into_iter();
let (key, id) = get_next_and_parse_property!(iter, Text);
if key != "Id" {
return Err(ResponseParserError::UnexpectedProperty(key));
return Err(ResponseParserError::UnexpectedProperty(key.to_string()));
}
Ok(AddIdResponse { id })
}

View File

@@ -22,7 +22,7 @@ impl CommandResponse<'_> for PlaylistResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
unimplemented!()
}
}

View File

@@ -105,7 +105,7 @@ impl CommandResponse<'_> for PlaylistFindResponse {
todo!()
}
fn parse(_parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(_parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
unimplemented!()
}
}

View File

@@ -30,7 +30,7 @@ impl CommandResponse<'_> for PlaylistIdResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
unimplemented!()
}
}

View File

@@ -33,7 +33,7 @@ impl CommandResponse<'_> for PlaylistInfoResponse {
todo!()
}
fn parse(_parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(_parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
unimplemented!()
}
}

View File

@@ -104,7 +104,7 @@ impl CommandResponse<'_> for PlaylistSearchResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
unimplemented!()
}
}

View File

@@ -78,7 +78,7 @@ impl CommandResponse<'_> for PlChangesResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
unimplemented!()
}
}

View File

@@ -77,7 +77,7 @@ impl CommandResponse<'_> for PlChangesPosIdResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
unimplemented!()
}
}

View File

@@ -27,7 +27,7 @@ impl CommandResponse<'_> for ConfigResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
let music_directory = get_property!(parts, "music_directory", Text).to_string();

View File

@@ -28,7 +28,7 @@ impl CommandResponse<'_> for DecodersResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let mut result = Vec::new();
let mut current_decoder: Option<Decoder> = None;
for (key, value) in parts.into_vec()?.into_iter() {
@@ -49,19 +49,19 @@ impl CommandResponse<'_> for DecodersResponse {
"suffix" => {
current_decoder
.as_mut()
.ok_or(ResponseParserError::SyntaxError(0, key))?
.ok_or(ResponseParserError::SyntaxError(0, key.to_string()))?
.suffixes
.push(expect_property_type!(Some(value), key, Text).to_string());
}
"mime_type" => {
current_decoder
.as_mut()
.ok_or(ResponseParserError::SyntaxError(0, key))?
.ok_or(ResponseParserError::SyntaxError(0, key.to_string()))?
.mime_types
.push(expect_property_type!(Some(value), key, Text).to_string());
}
k => {
return Err(ResponseParserError::UnexpectedProperty(k));
return Err(ResponseParserError::UnexpectedProperty(k.to_string()));
}
}
}

View File

@@ -130,7 +130,7 @@ impl CommandResponse<'_> for StickerFindResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: Vec<_> = parts.into_vec()?;
let mut stickers = Vec::with_capacity(parts.len() / 2);
@@ -144,7 +144,9 @@ impl CommandResponse<'_> for StickerFindResponse {
.ok_or(ResponseParserError::UnexpectedEOF)?;
if sticker.0 != "sticker" {
return Err(ResponseParserError::UnexpectedProperty(sticker.0));
return Err(ResponseParserError::UnexpectedProperty(
sticker.0.to_string(),
));
}
// TODO: check that this is a valid sticker type
// debug_assert!(uri.0 == "");
@@ -157,11 +159,11 @@ impl CommandResponse<'_> for StickerFindResponse {
let mut split = sticker.split("=");
let name = split
.next()
.ok_or(ResponseParserError::SyntaxError(0, sticker))?
.ok_or(ResponseParserError::SyntaxError(0, sticker.to_string()))?
.to_string();
let value = split
.next()
.ok_or(ResponseParserError::SyntaxError(1, sticker))?
.ok_or(ResponseParserError::SyntaxError(1, sticker.to_string()))?
.to_string();
stickers.push(StickerFindResponseEntry { uri, name, value });

View File

@@ -66,19 +66,22 @@ impl CommandResponse<'_> for StickerListResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: Vec<_> = parts.into_vec()?;
if let Some((k, _)) = parts.iter().find(|(k, _)| *k != "sticker") {
return Err(ResponseParserError::UnexpectedProperty(k));
return Err(ResponseParserError::UnexpectedProperty(k.to_string()));
}
let result = parts
.iter()
.map(|(_, v)| match v {
GenericResponseValue::Text(value) => Ok(value),
GenericResponseValue::Binary(_) => Err(
ResponseParserError::UnexpectedPropertyType("sticker", "Binary"),
),
GenericResponseValue::Binary(_) => {
Err(ResponseParserError::UnexpectedPropertyType(
"sticker".to_string(),
"Binary".to_string(),
))
}
})
.collect::<Result<Vec<_>, ResponseParserError>>()?;
@@ -88,8 +91,12 @@ impl CommandResponse<'_> for StickerListResponse {
// TODO: This assumes the first = is the only one.
// See: https://github.com/MusicPlayerDaemon/MPD/issues/2166
let mut split = v.split('=');
let key = split.next().ok_or(ResponseParserError::SyntaxError(0, v))?;
let value = split.next().ok_or(ResponseParserError::SyntaxError(1, v))?;
let key = split
.next()
.ok_or(ResponseParserError::SyntaxError(0, v.to_string()))?;
let value = split
.next()
.ok_or(ResponseParserError::SyntaxError(1, v.to_string()))?;
Ok((key.to_string(), value.to_string()))
})
.collect::<Result<HashMap<_, _>, ResponseParserError>>()

View File

@@ -66,7 +66,7 @@ impl CommandResponse<'_> for StickerNamesTypesResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: Vec<_> = parts.into_vec()?;
let mut result = HashMap::new();
@@ -76,10 +76,14 @@ impl CommandResponse<'_> for StickerNamesTypesResponse {
let (type_key, type_value) = name_type_pair[1];
if name_key != "name" {
return Err(ResponseParserError::UnexpectedProperty(name_key));
return Err(ResponseParserError::UnexpectedProperty(
name_key.to_string(),
));
}
if type_key != "type" {
return Err(ResponseParserError::UnexpectedProperty(type_key));
return Err(ResponseParserError::UnexpectedProperty(
type_key.to_string(),
));
}
let name = expect_property_type!(Some(name_value), "name", Text).to_string();

View File

@@ -70,7 +70,7 @@ impl CommandResponse<'_> for ListPlaylistInfoResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
unimplemented!()
}
}

View File

@@ -29,7 +29,7 @@ impl CommandResponse<'_> for ListPlaylistsResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let mut parts = parts.into_lazy_vec().into_iter().peekable();
// TODO: count instances of 'playlist' to preallocate
let mut result = Vec::new();

View File

@@ -27,7 +27,7 @@ impl CommandResponse<'_> for PlaylistLengthResponse {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
let songs = get_and_parse_property!(parts, "songs", Text);

View File

@@ -36,8 +36,8 @@ pub enum Request {
// -- Playback Control Commands -- //
Next,
Pause(Option<bool>),
Play(SongPosition),
PlayId(SongId),
Play(Option<SongPosition>),
PlayId(Option<SongId>),
Previous,
Seek(SongPosition, TimeWithFractions),
SeekId(SongId, TimeWithFractions),

View File

@@ -33,29 +33,27 @@ impl<'a> ResponseAttributes<'a> {
pub fn into_map(
self,
) -> Result<HashMap<&'a str, GenericResponseValue<'a>>, ResponseParserError<'a>> {
) -> Result<HashMap<&'a str, GenericResponseValue<'a>>, ResponseParserError> {
self.into()
}
pub fn into_vec(
self,
) -> Result<Vec<(&'a str, GenericResponseValue<'a>)>, ResponseParserError<'a>> {
pub fn into_vec(self) -> Result<Vec<(&'a str, GenericResponseValue<'a>)>, ResponseParserError> {
self.into()
}
pub fn into_lazy_vec(
self,
) -> Vec<Result<(&'a str, GenericResponseValue<'a>), ResponseParserError<'a>>> {
) -> Vec<Result<(&'a str, GenericResponseValue<'a>), ResponseParserError>> {
self.into()
}
pub fn verify_all_keys_equal(&self, expected_key: &str) -> Result<(), ResponseParserError<'a>> {
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));
return Err(ResponseParserError::UnexpectedProperty(key.to_string()));
}
}
Ok(())
@@ -67,7 +65,7 @@ impl<'a> ResponseAttributes<'a> {
}
impl<'a> Iterator for ResponseAttributes<'a> {
type Item = Result<(&'a str, GenericResponseValue<'a>), ResponseParserError<'a>>;
type Item = Result<(&'a str, GenericResponseValue<'a>), ResponseParserError>;
fn next(&mut self) -> Option<Self::Item> {
if self.cursor >= self.bytestring.len() {
@@ -121,7 +119,7 @@ impl<'a> Iterator for ResponseAttributes<'a> {
// TODO: throw more specific error
return Some(Err(ResponseParserError::SyntaxError(
0,
"Invalid byte count",
"Invalid byte count".to_string(),
)));
}
};
@@ -131,7 +129,7 @@ impl<'a> Iterator for ResponseAttributes<'a> {
// TODO: throw more specific error
return Some(Err(ResponseParserError::SyntaxError(
0,
"Invalid byte count",
"Invalid byte count".to_string(),
)));
}
};
@@ -170,14 +168,14 @@ impl<'a> Iterator for ResponseAttributes<'a> {
}
impl<'a> From<ResponseAttributes<'a>>
for Result<HashMap<&'a str, GenericResponseValue<'a>>, ResponseParserError<'a>>
for Result<HashMap<&'a str, GenericResponseValue<'a>>, 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));
return Err(ResponseParserError::DuplicateProperty(k.to_string()));
}
map.insert(k, v);
}
@@ -186,7 +184,7 @@ impl<'a> From<ResponseAttributes<'a>>
}
impl<'a> From<ResponseAttributes<'a>>
for Vec<Result<(&'a str, GenericResponseValue<'a>), ResponseParserError<'a>>>
for Vec<Result<(&'a str, GenericResponseValue<'a>), ResponseParserError>>
{
fn from(val: ResponseAttributes<'a>) -> Self {
val.collect()
@@ -194,7 +192,7 @@ impl<'a> From<ResponseAttributes<'a>>
}
impl<'a> From<ResponseAttributes<'a>>
for Result<Vec<(&'a str, GenericResponseValue<'a>)>, ResponseParserError<'a>>
for Result<Vec<(&'a str, GenericResponseValue<'a>)>, ResponseParserError>
{
fn from(val: ResponseAttributes<'a>) -> Self {
val.collect()
@@ -220,8 +218,8 @@ macro_rules! _expect_property_type {
};
return Err(
crate::commands::ResponseParserError::UnexpectedPropertyType(
$name,
actual_type,
$name.to_string(),
actual_type.to_string(),
),
);
}
@@ -235,7 +233,10 @@ macro_rules! _parse_optional_property_type {
$property
.map(|value| {
value.parse().map_err(|_| {
crate::commands::ResponseParserError::InvalidProperty($name, value)
crate::commands::ResponseParserError::InvalidProperty(
$name.to_string(),
value.to_string(),
)
})
})
.transpose()?
@@ -246,7 +247,11 @@ macro_rules! _unwrap_optional_property_type {
($name:expr, $property:expr) => {
match $property {
Some(value) => value,
None => return Err(crate::commands::ResponseParserError::MissingProperty($name)),
None => {
return Err(crate::commands::ResponseParserError::MissingProperty(
$name.to_string(),
))
}
}
};
}
@@ -402,7 +407,7 @@ mod tests {
#[test]
#[cfg(debug_assertions)]
fn test_valid_hashmap_uniqueness_assert() -> Result<(), ResponseParserError<'static>> {
fn test_valid_hashmap_uniqueness_assert() -> Result<(), ResponseParserError> {
let raw_response = indoc! {
"a: 1
A: 2
@@ -431,7 +436,7 @@ mod tests {
}
#[test]
fn test_response_attributes_single_attribute() -> Result<(), ResponseParserError<'static>> {
fn test_response_attributes_single_attribute() -> Result<(), ResponseParserError> {
let raw_response = indoc! {
"name: Sticker1
OK"
@@ -447,7 +452,7 @@ mod tests {
}
#[test]
fn test_response_attributes_multiple_attributes() -> Result<(), ResponseParserError<'static>> {
fn test_response_attributes_multiple_attributes() -> Result<(), ResponseParserError> {
let raw_response = indoc! {
"name: Sticker1
type: emoji
@@ -467,7 +472,7 @@ mod tests {
}
#[test]
fn test_response_attributes_empty_response() -> Result<(), ResponseParserError<'static>> {
fn test_response_attributes_empty_response() -> Result<(), ResponseParserError> {
let raw_response = indoc! {
"OK"
};
@@ -478,7 +483,7 @@ mod tests {
}
#[test]
fn test_response_attributes_unexpected_eof() -> Result<(), ResponseParserError<'static>> {
fn test_response_attributes_unexpected_eof() -> Result<(), ResponseParserError> {
let raw_response = indoc! {
"name: Sticker1
type: emoji"
@@ -490,7 +495,7 @@ mod tests {
}
#[test]
fn test_response_attributes_repeated_attribute() -> Result<(), ResponseParserError<'static>> {
fn test_response_attributes_repeated_attribute() -> Result<(), ResponseParserError> {
let raw_response = indoc! {
"name: Sticker1
name: Sticker2
@@ -505,7 +510,7 @@ mod tests {
}
#[test]
fn test_response_attributes_empty_line() -> Result<(), ResponseParserError<'static>> {
fn test_response_attributes_empty_line() -> Result<(), ResponseParserError> {
let raw_response = indoc! {
"name: Sticker1
@@ -524,7 +529,7 @@ mod tests {
}
#[test]
fn test_response_attributes_no_value() -> Result<(), ResponseParserError<'static>> {
fn test_response_attributes_no_value() -> Result<(), ResponseParserError> {
let raw_response = indoc! {
"name:
type: emoji
@@ -539,7 +544,7 @@ mod tests {
}
#[test]
fn test_response_attributes_binary_data() -> Result<(), ResponseParserError<'static>> {
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();

View File

@@ -21,18 +21,18 @@ impl DbDirectoryInfo {
self.last_modified.is_some()
}
pub fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
pub fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
Self::parse_map(parts)
}
pub(crate) fn parse_map<'a>(
parts: HashMap<&str, GenericResponseValue<'a>>,
) -> Result<Self, ResponseParserError<'a>> {
) -> Result<Self, ResponseParserError> {
let directory = get_property!(parts, "directory", Text);
let directory = directory
.parse()
.map_err(|_| ResponseParserError::InvalidProperty("directory", directory))?;
let directory = directory.parse().map_err(|_| {
ResponseParserError::InvalidProperty("directory".to_string(), directory.to_string())
})?;
let last_modified =
get_optional_property!(parts, "Last-Modified", Text).map(|s| s.to_owned());

View File

@@ -21,18 +21,18 @@ impl DbPlaylistInfo {
self.last_modified.is_some()
}
pub fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
pub fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
Self::parse_map(parts)
}
pub(crate) fn parse_map<'a>(
parts: HashMap<&str, GenericResponseValue<'a>>,
) -> Result<Self, ResponseParserError<'a>> {
) -> Result<Self, ResponseParserError> {
let playlist = get_property!(parts, "playlist", Text);
let playlist = playlist
.parse()
.map_err(|_| ResponseParserError::InvalidProperty("playlist", playlist))?;
let playlist = playlist.parse().map_err(|_| {
ResponseParserError::InvalidProperty("playlist".to_string(), playlist.to_string())
})?;
let last_modified =
get_optional_property!(parts, "Last-Modified", Text).map(|s| s.to_owned());

View File

@@ -26,7 +26,7 @@ impl DbSelectionPrintResponse {
pub fn parse(
parts: ResponseAttributes<'_>,
) -> Result<Vec<DbSelectionPrintResponse>, ResponseParserError<'_>> {
) -> Result<Vec<DbSelectionPrintResponse>, ResponseParserError> {
debug_assert!(!parts.is_empty());
let vec: Vec<_> = parts.into_vec()?;
@@ -48,9 +48,9 @@ impl DbSelectionPrintResponse {
"playlist" => {
DbPlaylistInfo::parse_map(attrs).map(DbSelectionPrintResponse::Playlist)
}
p => Err(ResponseParserError::UnexpectedProperty(p)),
p => Err(ResponseParserError::UnexpectedProperty(p.to_string())),
})
.collect::<Result<Vec<DbSelectionPrintResponse>, ResponseParserError<'_>>>()
.collect::<Result<Vec<DbSelectionPrintResponse>, ResponseParserError>>()
}
}

View File

@@ -47,17 +47,17 @@ impl DbSongInfo {
|| self.playlist.is_some()
}
pub fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
pub fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
Self::parse_map(parts)
}
pub(crate) fn parse_map<'a>(
mut parts: HashMap<&'a str, GenericResponseValue<'a>>,
) -> Result<Self, ResponseParserError<'a>> {
) -> Result<Self, ResponseParserError> {
let file: PathBuf = match remove_and_parse_next_optional_property!(parts, "file", Text) {
Some(f) => f,
None => return Err(ResponseParserError::MissingProperty("file")),
None => return Err(ResponseParserError::MissingProperty("file".to_string())),
};
if parts.is_empty() {
@@ -88,7 +88,7 @@ impl DbSongInfo {
let value = expect_property_type!(Some(value), key, Text).to_string();
Ok(Tag::new(key.to_string(), value))
})
.collect::<Result<Vec<Tag>, ResponseParserError<'_>>>()?;
.collect::<Result<Vec<Tag>, ResponseParserError>>()?;
tags.sort_unstable();
Ok(DbSongInfo {