404 lines
12 KiB
Rust
404 lines
12 KiB
Rust
use std::{
|
|
collections::{HashMap, HashSet},
|
|
str::SplitWhitespace,
|
|
};
|
|
|
|
use crate::Request;
|
|
|
|
mod audio_output_devices;
|
|
mod client_to_client;
|
|
mod connection_settings;
|
|
mod controlling_playback;
|
|
mod mounts_and_neighbors;
|
|
mod music_database;
|
|
mod partition_commands;
|
|
mod playback_options;
|
|
mod querying_mpd_status;
|
|
mod queue;
|
|
mod reflection;
|
|
mod stickers;
|
|
mod stored_playlists;
|
|
|
|
pub use audio_output_devices::*;
|
|
pub use client_to_client::*;
|
|
pub use connection_settings::*;
|
|
pub use controlling_playback::*;
|
|
pub use mounts_and_neighbors::*;
|
|
pub use music_database::*;
|
|
pub use partition_commands::*;
|
|
pub use playback_options::*;
|
|
pub use querying_mpd_status::*;
|
|
pub use queue::*;
|
|
pub use reflection::*;
|
|
pub use stickers::*;
|
|
pub use stored_playlists::*;
|
|
|
|
pub trait Command {
|
|
type Response;
|
|
// The command name used within the protocol
|
|
const COMMAND: &'static str;
|
|
|
|
// TODO: `parse_request` should be using a more custom splitter, that can handle
|
|
// quoted strings and escape characters. This is what mpd uses to provide arguments
|
|
// with spaces and whatnot.
|
|
|
|
// A function to parse the remaining parts of the command, split by whitespace
|
|
fn parse_request(parts: SplitWhitespace<'_>) -> RequestParserResult<'_>;
|
|
|
|
fn parse_raw_request(raw: &str) -> RequestParserResult<'_> {
|
|
let (line, rest) = raw
|
|
.split_once('\n')
|
|
.ok_or(RequestParserError::UnexpectedEOF)?;
|
|
let mut parts = line.split_whitespace();
|
|
|
|
let command_name = parts
|
|
.next()
|
|
.ok_or(RequestParserError::SyntaxError(0, line.to_string()))?
|
|
.trim();
|
|
|
|
debug_assert!(command_name == Self::COMMAND);
|
|
|
|
Self::parse_request(parts).map(|(req, _)| (req, rest))
|
|
}
|
|
|
|
fn parse_response(
|
|
parts: ResponseAttributes<'_>,
|
|
) -> Result<Self::Response, ResponseParserError<'_>>;
|
|
|
|
fn parse_raw_response(raw: &str) -> Result<Self::Response, ResponseParserError<'_>> {
|
|
Self::parse_response(ResponseAttributes::new(raw)?)
|
|
}
|
|
}
|
|
|
|
pub type RequestParserResult<'a> = Result<(Request, &'a str), RequestParserError>;
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub enum RequestParserError {
|
|
SyntaxError(u64, String),
|
|
MissingCommandListEnd(u64),
|
|
NestedCommandList(u64),
|
|
UnexpectedCommandListEnd(u64),
|
|
UnexpectedEOF,
|
|
MissingNewline,
|
|
}
|
|
|
|
// 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),
|
|
|
|
// 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),
|
|
|
|
/// A property was found in the response that was not expected.
|
|
UnexpectedProperty(&'a str),
|
|
|
|
/// The property value is parsable, but the value is invalid or nonsensical.
|
|
InvalidProperty(&'a str, &'a str),
|
|
|
|
/// Could not parse the response due to a syntax error.
|
|
SyntaxError(u64, &'a str),
|
|
|
|
/// Response ended early, while more properties were expected.
|
|
UnexpectedEOF,
|
|
// MissingNewline,
|
|
}
|
|
|
|
pub type GenericResponseResult<'a> = Result<GenericResponse<'a>, &'a str>;
|
|
|
|
pub type GenericResponse<'a> = HashMap<&'a str, GenericResponseValue<'a>>;
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
pub enum GenericResponseValue<'a> {
|
|
Text(&'a str),
|
|
Binary(&'a [u8]),
|
|
// Many(Vec<GenericResponseValue<'a>>),
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub struct ResponseAttributes<'a>(Vec<(&'a str, GenericResponseValue<'a>)>);
|
|
|
|
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;
|
|
}
|
|
|
|
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)));
|
|
}
|
|
|
|
Ok(parts.into())
|
|
}
|
|
|
|
// 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 ResponseAttributes<'_> {
|
|
pub fn is_empty(&self) -> bool {
|
|
self.0.is_empty()
|
|
}
|
|
}
|
|
|
|
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>> {
|
|
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()
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
// 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::commands::GenericResponseValue::$variant(value)) => Some(value),
|
|
Some(value) => {
|
|
let actual_type = match value {
|
|
crate::commands::GenericResponseValue::Text(_) => "Text",
|
|
crate::commands::GenericResponseValue::Binary(_) => "Binary",
|
|
};
|
|
return Err(
|
|
crate::commands::ResponseParserError::UnexpectedPropertyType(
|
|
$name,
|
|
actual_type,
|
|
),
|
|
);
|
|
}
|
|
None => None,
|
|
}
|
|
};
|
|
}
|
|
|
|
macro_rules! _parse_optional_property_type {
|
|
($name:expr, $property:expr) => {
|
|
$property
|
|
.map(|value| {
|
|
value.parse().map_err(|_| {
|
|
crate::commands::ResponseParserError::InvalidProperty($name, value)
|
|
})
|
|
})
|
|
.transpose()?
|
|
};
|
|
}
|
|
|
|
macro_rules! _unwrap_optional_property_type {
|
|
($name:expr, $property:expr) => {
|
|
match $property {
|
|
Some(value) => value,
|
|
None => return Err(crate::commands::ResponseParserError::MissingProperty($name)),
|
|
}
|
|
};
|
|
}
|
|
|
|
macro_rules! expect_optional_property_type {
|
|
($property:expr, $name:expr, $variant:ident) => {
|
|
crate::commands::_expect_property_type!($property, $name, $variant)
|
|
};
|
|
}
|
|
|
|
macro_rules! expect_property_type {
|
|
($property:expr, $name:expr, $variant:ident) => {{
|
|
let prop = crate::commands::_expect_property_type!($property, $name, $variant);
|
|
crate::commands::_unwrap_optional_property_type!($name, prop)
|
|
}};
|
|
}
|
|
|
|
macro_rules! get_optional_property {
|
|
($parts:expr, $name:literal, $variant:ident) => {
|
|
crate::commands::_expect_property_type!({ $parts.get($name).map(|v| *v) }, $name, $variant)
|
|
};
|
|
}
|
|
|
|
macro_rules! get_property {
|
|
($parts:expr, $name:literal, $variant:ident) => {{
|
|
let prop = crate::commands::_expect_property_type!(
|
|
{ $parts.get($name).map(|v| *v) },
|
|
$name,
|
|
$variant
|
|
);
|
|
crate::commands::_unwrap_optional_property_type!($name, prop)
|
|
}};
|
|
}
|
|
|
|
macro_rules! get_and_parse_optional_property {
|
|
($parts:ident, $name:literal, $variant:ident) => {{
|
|
let prop = crate::commands::_expect_property_type!(
|
|
{ $parts.get($name).map(|v| *v) },
|
|
$name,
|
|
$variant
|
|
);
|
|
crate::commands::_parse_optional_property_type!($name, prop)
|
|
}};
|
|
}
|
|
|
|
macro_rules! get_and_parse_property {
|
|
($parts:ident, $name:literal, $variant:ident) => {{
|
|
let prop = crate::commands::_expect_property_type!(
|
|
{ $parts.get($name).map(|v| *v) },
|
|
$name,
|
|
$variant
|
|
);
|
|
let prop = crate::commands::_parse_optional_property_type!($name, prop);
|
|
crate::commands::_unwrap_optional_property_type!($name, prop)
|
|
}};
|
|
}
|
|
|
|
macro_rules! get_next_optional_property {
|
|
($parts:ident, $variant:ident) => {
|
|
match $parts.next() {
|
|
Some((name, value)) => {
|
|
crate::commands::_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((name, value)) => (
|
|
name,
|
|
crate::commands::_expect_property_type!({ Some(value) }, name, $variant).unwrap(),
|
|
),
|
|
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::commands::_expect_property_type!({ Some(value) }, name, $variant);
|
|
prop.map(|value| {
|
|
(
|
|
name,
|
|
crate::commands::_parse_optional_property_type!(name, value),
|
|
)
|
|
})
|
|
}
|
|
None => None,
|
|
}
|
|
};
|
|
}
|
|
|
|
macro_rules! get_next_and_parse_property {
|
|
($parts:ident, $variant:ident) => {
|
|
match $parts.next() {
|
|
Some((name, value)) => {
|
|
let prop = crate::commands::_expect_property_type!({ Some(value) }, name, $variant);
|
|
let prop = crate::commands::_parse_optional_property_type!(name, prop);
|
|
(
|
|
name,
|
|
crate::commands::_unwrap_optional_property_type!(name, prop),
|
|
)
|
|
}
|
|
None => return Err(crate::commands::ResponseParserError::UnexpectedEOF),
|
|
}
|
|
};
|
|
}
|
|
|
|
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;
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
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();
|
|
assert_eq!(map.len(), 4);
|
|
}
|
|
|
|
#[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 map: HashMap<_, _> = invalid_maplike_attrs.into();
|
|
assert_eq!(map.len(), 4);
|
|
}
|
|
}
|