248 lines
7.7 KiB
Rust
248 lines
7.7 KiB
Rust
use std::{collections::HashMap, str::SplitWhitespace};
|
|
|
|
use crate::Request;
|
|
|
|
mod audio_output_devices;
|
|
mod client_to_client;
|
|
mod controlling_playback;
|
|
mod playback_options;
|
|
mod querying_mpd_status;
|
|
mod queue;
|
|
|
|
pub use audio_output_devices::*;
|
|
pub use client_to_client::*;
|
|
pub use controlling_playback::*;
|
|
pub use playback_options::*;
|
|
pub use querying_mpd_status::*;
|
|
pub use queue::*;
|
|
|
|
pub trait Command {
|
|
type Response;
|
|
// The command name used within the protocol
|
|
const COMMAND: &'static str;
|
|
|
|
// 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
|
|
.next()
|
|
.ok_or(ResponseParserError::SyntaxError(0, line))?;
|
|
|
|
// TODO: handle binary data
|
|
let value = keyval
|
|
.next()
|
|
.ok_or(ResponseParserError::SyntaxError(0, line))?;
|
|
|
|
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,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub enum ResponseParserError<'a> {
|
|
MissingProperty(&'a str),
|
|
UnexpectedPropertyType(&'a str, &'a str),
|
|
InvalidProperty(&'a str, &'a str),
|
|
SyntaxError(u64, &'a str),
|
|
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)]
|
|
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 {
|
|
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)
|
|
}
|
|
}
|
|
|
|
/*******************/
|
|
/* 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 {
|
|
($parts:expr, $name:literal, $variant:ident) => {
|
|
match $parts.get($name) {
|
|
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! get_and_parse_property {
|
|
($parts:ident, $name:literal, $variant:ident) => {
|
|
match $parts.get($name) {
|
|
Some(crate::commands::GenericResponseValue::$variant(value)) => (*value)
|
|
.parse()
|
|
.map_err(|_| crate::commands::ResponseParserError::InvalidProperty($name, 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_and_parse_optional_property {
|
|
($parts:ident, $name:literal, $variant:ident) => {
|
|
match $parts.get($name) {
|
|
Some(crate::commands::GenericResponseValue::$variant(value)) => {
|
|
Some((*value).parse().map_err(|_| {
|
|
crate::commands::ResponseParserError::InvalidProperty($name, 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,
|
|
}
|
|
};
|
|
}
|
|
|
|
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;
|