Lines
53.45 %
Functions
12.24 %
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<'_>> {
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
.ok_or(ResponseParserError::SyntaxError(0, line))?;
// TODO: handle binary data, also verify binarylimit
let value = keyval
parts.push((key, GenericResponseValue::Text(value)));
Self::parse_response(parts.into())
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"?
pub enum ResponseParserError<'a> {
MissingProperty(&'a str),
UnexpectedPropertyType(&'a str, &'a str),
UnexpectedProperty(&'a str),
InvalidProperty(&'a str, &'a str),
SyntaxError(u64, &'a str),
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>>),
pub struct ResponseAttributes<'a>(Vec<(&'a str, GenericResponseValue<'a>)>);
// impl ResponseAttributes<'_> {
// pub fn
// 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>)> {
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! get_property {
($parts:expr, $name:literal, $variant:ident) => {
match $parts.get($name) {
Some(crate::commands::GenericResponseValue::$variant(value)) => *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 => return Err(crate::commands::ResponseParserError::MissingProperty($name)),
macro_rules! get_optional_property {
Some(crate::commands::GenericResponseValue::$variant(value)) => Some(*value),
None => None,
macro_rules! get_and_parse_property {
($parts:ident, $name:literal, $variant:ident) => {
Some(crate::commands::GenericResponseValue::$variant(value)) => (*value)
.parse()
.map_err(|_| crate::commands::ResponseParserError::InvalidProperty($name, value))?,
macro_rules! get_and_parse_optional_property {
Some(crate::commands::GenericResponseValue::$variant(value)) => {
Some((*value).parse().map_err(|_| {
crate::commands::ResponseParserError::InvalidProperty($name, value)
})?)
pub(crate) use get_and_parse_optional_property;
pub(crate) use get_and_parse_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);
#[should_panic]
fn test_invalid_hashmap_uniqueness_assert() {
let invalid_maplike_attrs: ResponseAttributes = vec![
("b", GenericResponseValue::Text("2")),
("c", GenericResponseValue::Text("3")),
("a", GenericResponseValue::Text("4")),
let map: HashMap<_, _> = invalid_maplike_attrs.into();