Files
empidee/src/commands.rs

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;