commands: split Command trait into req + res parts

This commit is contained in:
2025-12-05 22:54:01 +09:00
parent 380071b66c
commit e24451900f
147 changed files with 3748 additions and 3578 deletions

View File

@@ -28,73 +28,395 @@ pub use reflection::*;
pub use stickers::*;
pub use stored_playlists::*;
/// A trait modelling the request/response pair of a single MPD command.
pub trait Command {
/// The request sent from the client to the server
type Request;
/// The response sent from the server to the client
type Response;
/// A trait modelling a single MPD command request.
pub trait CommandRequest<'a>
where
Self: Sized,
{
/// The command name used within the protocol
const COMMAND: &'static str;
/// Converts this specific request type to it's corresponding variant in the generic Request enum.
fn into_request_enum(self) -> crate::Request;
/// Converts from the generic Request enum to this specific request type.
///
/// If the enum variant does not match this type, returns None.
fn from_request_enum(request: crate::Request) -> Option<Self>;
/// Serializes the request into a String.
fn serialize(&self) -> String;
/// Parses the request from its tokenized parts.
/// See also [`parse_raw`].
fn parse(parts: RequestTokenizer<'a>) -> Result<Self, RequestParserError>;
/// Parses the request from its raw string representation.
///
/// This assumes the raw string starts with the command name, e.g.
/// `command_name arg1 "arg2 arg3"`
fn parse_raw(raw: &'a str) -> Result<Self, RequestParserError> {
let (line, rest) = raw
.split_once('\n')
.ok_or(RequestParserError::UnexpectedEOF)?;
debug_assert!(rest.is_empty());
let mut tokenized = RequestTokenizer::new(line);
let command_name_token_length = Self::COMMAND.split_ascii_whitespace().count();
let mut command_name = Vec::with_capacity(command_name_token_length);
for _ in 0..command_name_token_length {
let token = tokenized
.next()
.ok_or(RequestParserError::SyntaxError(0, line.to_string()))?;
command_name.push(token);
}
let command_name = command_name.join(" ");
if command_name != Self::COMMAND {
return Err(RequestParserError::SyntaxError(0, line.to_string()));
}
Self::parse(tokenized)
}
}
/// A trait modelling a single MPD command response.
pub trait CommandResponse<'a>
where
Self: Sized,
{
/// Converts this specific response type to it's corresponding variant in the generic Response enum.
fn into_response_enum(self) -> crate::Response;
/// Converts from the generic Response enum to this specific response type.
///
/// If the enum variant does not match this type, returns None.
fn from_response_enum(response: crate::Response) -> Option<Self>;
// /// Serializes the response into a Vec<u8>.
// fn serialize(&self) -> Vec<u8>;
/// Parses the response from its tokenized parts.
/// See also [`parse_raw`].
fn parse(parts: ResponseAttributes<'a>) -> Result<Self, ResponseParserError<'a>>;
/// Parses the response from its raw byte representation.
fn parse_raw(raw: &'a [u8]) -> Result<Self, ResponseParserError<'a>> {
Self::parse(ResponseAttributes::new_from_bytes(raw))
}
}
/// A trait modelling the request/response pair of a single MPD command.
pub trait Command<'req, 'res> {
/// The request sent from the client to the server
type Request: CommandRequest<'req>;
/// The response sent from the server to the client
type Response: CommandResponse<'res>;
/// The command name used within the protocol
const COMMAND: &'static str = Self::Request::COMMAND;
/// Serialize the request into a string.
/// This should optimally produce an input that can be parsed by [`parse_request`]
fn serialize_request(&self, request: Self::Request) -> String;
fn serialize_request(&self, request: Self::Request) -> String {
request.serialize().to_owned()
}
/// Serialize the request into a bytestring.
fn serialize_request_to_bytes(&self, request: Self::Request) -> Vec<u8> {
self.serialize_request(request).into_bytes()
}
// fn serialize_response(&self) -> String;
// fn serialize_response_to_bytes(&self) -> Vec<u8> {
// self.serialize_response().into_bytes()
// }
/// Parse the request from its tokenized parts.
///
/// Note that this assumes only the parts after the command name are passed in, e.g.
///
/// ```ignore
/// arg1 "arg2 arg3"
/// ```
fn parse_request(parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError>;
/// Parse the raw request string into a request and the remaining unparsed string.
/// This assumes the raw string starts with the command name, e.g.
///
/// ```ignore
/// command_name arg1 "arg2 arg3"
/// ```
fn parse_raw_request(raw: &str) -> Result<(Self::Request, &str), RequestParserError> {
let (line, rest) = raw
.split_once('\n')
.ok_or(RequestParserError::UnexpectedEOF)?;
let mut tokenized = RequestTokenizer::new(line);
let command_name = tokenized
.next()
.ok_or(RequestParserError::SyntaxError(0, line.to_string()))?
.trim();
debug_assert!(command_name == Self::COMMAND);
Self::parse_request(tokenized).map(|req| (req, rest))
/// Parse the request from its tokenized parts. See also [`parse_raw_request`].
fn parse_request(parts: RequestTokenizer<'req>) -> Result<Self::Request, RequestParserError> {
Self::Request::parse(parts)
}
/// Parse the response from its tokenized parts.
/// See also [`parse_raw_response`].
fn parse_response(parts: ResponseAttributes<'_>) -> ResponseParserResult<'_, Self::Response>;
/// Parse the raw request string into a request.
/// This assumes the raw string starts with the command name, e.g. `command_name arg1 "arg2 arg3"`
fn parse_raw_request(raw: &'req str) -> Result<Self::Request, RequestParserError> {
Self::Request::parse_raw(raw)
}
// /// Serialize the response into a string.
// fn serialize_response(&self, response: Self::Response) -> String {
/// Parse the response from its tokenized parts. See also [`parse_raw_response`].
fn parse_response(
parts: ResponseAttributes<'res>,
) -> Result<Self::Response, ResponseParserError<'res>> {
Self::Response::parse(parts)
}
/// Parse the raw response string into a response.
fn parse_raw_response(raw: &str) -> ResponseParserResult<'_, Self::Response> {
Self::parse_response(ResponseAttributes::new(raw))
fn parse_raw_response(raw: &'res [u8]) -> Result<Self::Response, ResponseParserError<'res>> {
Self::Response::parse_raw(raw)
}
}
// pub type RequestParserResult<'a> = ;
// Request/response implementation helpers
macro_rules! empty_command_request {
($name:ident, $command_name:expr) => {
paste::paste! {
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct [<$name Request>];
}
impl crate::commands::CommandRequest<'_> for paste::paste! { [<$name Request>] } {
const COMMAND: &'static str = $command_name;
fn into_request_enum(self) -> crate::Request {
match Self::COMMAND {
$command_name => crate::Request::$name,
_ => unimplemented!(),
}
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match (Self::COMMAND, request) {
($command_name, crate::Request::$name) => {
Some(paste::paste! { [<$name Request>] })
}
_ => None,
}
}
fn serialize(&self) -> String {
Self::COMMAND.to_string()
}
fn parse(
mut parts: crate::commands::RequestTokenizer<'_>,
) -> Result<Self, crate::commands::RequestParserError> {
debug_assert!(parts.next().is_none());
Ok(paste::paste! { [<$name Request>] })
}
}
};
}
macro_rules! empty_command_response {
($name:ident) => {
paste::paste! {
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct [<$name Response>];
}
impl crate::commands::CommandResponse<'_> for paste::paste! { [<$name Response>] } {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn from_response_enum(_response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(
_parts: crate::commands::ResponseAttributes<'_>,
) -> Result<Self, crate::commands::ResponseParserError<'_>> {
debug_assert!(_parts.is_empty());
Ok(paste::paste! { [<$name Response>] })
}
}
};
}
macro_rules! single_item_command_request {
($name:ident, $command_name:expr, $item_type:ty) => {
paste::paste! {
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct [<$name Request>] ($item_type);
}
impl crate::commands::CommandRequest<'_> for paste::paste! { [<$name Request>] } {
const COMMAND: &'static str = $command_name;
fn into_request_enum(self) -> crate::Request {
match Self::COMMAND {
$command_name => crate::Request::$name(self.0),
_ => unimplemented!(),
}
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match (Self::COMMAND, request) {
($command_name, crate::Request::$name(item)) => {
Some(paste::paste! { [<$name Request>] ( item ) })
}
_ => None,
}
}
fn serialize(&self) -> String {
format!("{} {}", Self::COMMAND, self.0)
}
fn parse(
mut parts: crate::commands::RequestTokenizer<'_>,
) -> Result<Self, crate::commands::RequestParserError> {
let item_token = parts
.next()
.ok_or(crate::commands::RequestParserError::UnexpectedEOF)?;
let item = item_token.parse::<$item_type>().map_err(|_| {
crate::commands::RequestParserError::SyntaxError(0, item_token.to_owned())
})?;
debug_assert!(parts.next().is_none());
Ok(paste::paste! { [<$name Request>] ( item ) })
}
}
};
}
macro_rules! single_optional_item_command_request {
($name:ident, $command_name:expr, $item_type:ty) => {
paste::paste! {
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct [<$name Request>] (Option<$item_type>);
}
impl crate::commands::CommandRequest<'_> for paste::paste! { [<$name Request>] } {
const COMMAND: &'static str = $command_name;
fn into_request_enum(self) -> crate::Request {
match Self::COMMAND {
$command_name => crate::Request::$name(self.0),
_ => unimplemented!(),
}
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match (Self::COMMAND, request) {
($command_name, crate::Request::$name(item)) => {
Some(paste::paste! { [<$name Request>] ( item ) })
}
_ => None,
}
}
fn serialize(&self) -> String {
match &self.0 {
Some(item) => format!("{} {}", Self::COMMAND, item),
None => Self::COMMAND.to_string(),
}
}
fn parse(
mut parts: crate::commands::RequestTokenizer<'_>,
) -> Result<Self, crate::commands::RequestParserError> {
let item = parts
.next()
.map(|s| {
s.parse()
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))
})
.transpose()?;
debug_assert!(parts.next().is_none());
Ok(paste::paste! { [<$name Request>] ( item ) })
}
}
};
}
macro_rules! single_item_command_response {
($name:ident, $item_name:expr, $item_type:ty) => {
paste::paste! {
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct [<$name Response>] ( $item_type );
}
impl crate::commands::CommandResponse<'_> for paste::paste! { [<$name Response>] } {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn from_response_enum(_response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(
parts: crate::commands::ResponseAttributes<'_>,
) -> 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),
)?;
let item_ = crate::response_tokenizer::expect_property_type!(
Some(item_token),
$item_name,
Text
);
let item = item_.parse::<$item_type>().map_err(|_| {
crate::commands::ResponseParserError::InvalidProperty($item_name, item_)
})?;
Ok(paste::paste! { [<$name Response>] ( item ) })
}
}
};
}
macro_rules! multi_item_command_response {
($name:ident, $item_name:expr, $item_type:ty) => {
paste::paste! {
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct [<$name Response>] ( Vec<$item_type> );
}
impl crate::commands::CommandResponse<'_> for paste::paste! { [<$name Response>] } {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn from_response_enum(_response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(
parts: crate::commands::ResponseAttributes<'_>,
) -> 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));
}
let mut items = Vec::with_capacity(parts_.len());
let mut iter = parts_.into_iter();
while let Some(value) = iter.next() {
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,
)
})?;
items.push(parsed_value);
}
Ok(paste::paste! { [<$name Response>] ( items ) })
}
}
};
}
pub(crate) use empty_command_request;
pub(crate) use empty_command_response;
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;
#[derive(Debug, Clone, PartialEq)]
pub enum RequestParserError {

View File

@@ -1,11 +1,11 @@
pub mod disableoutput;
pub mod enableoutput;
pub mod outputs;
pub mod outputset;
pub mod toggleoutput;
mod disableoutput;
mod enableoutput;
mod outputs;
mod outputset;
mod toggleoutput;
pub use disableoutput::DisableOutput;
pub use enableoutput::EnableOutput;
pub use outputs::Outputs;
pub use outputset::OutputSet;
pub use toggleoutput::ToggleOutput;
pub use disableoutput::*;
pub use enableoutput::*;
pub use outputs::*;
pub use outputset::*;
pub use toggleoutput::*;

View File

@@ -1,38 +1,15 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, empty_command_response, single_item_command_request},
common::types::AudioOutputId,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct DisableOutput;
pub type DisableOutputRequest = AudioOutputId;
single_item_command_request!(DisableOutput, "disableoutput", AudioOutputId);
impl Command for DisableOutput {
empty_command_response!(DisableOutput);
impl Command<'_, '_> for DisableOutput {
type Request = DisableOutputRequest;
type Response = ();
const COMMAND: &'static str = "disableoutput";
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {}", Self::COMMAND, request)
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
let output_id = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let output_id = output_id
.parse()
.map_err(|_| RequestParserError::SyntaxError(0, output_id.to_owned()))?;
debug_assert!(parts.next().is_none());
Ok(output_id)
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
type Response = DisableOutputResponse;
}

View File

@@ -1,38 +1,15 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, empty_command_response, single_item_command_request},
common::types::AudioOutputId,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct EnableOutput;
pub type EnableOutputRequest = AudioOutputId;
single_item_command_request!(EnableOutput, "enableoutput", AudioOutputId);
impl Command for EnableOutput {
empty_command_response!(EnableOutput);
impl Command<'_, '_> for EnableOutput {
type Request = EnableOutputRequest;
type Response = ();
const COMMAND: &'static str = "enableoutput";
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {}", Self::COMMAND, request)
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
let output_id = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let output_id = output_id
.parse()
.map_err(|_| RequestParserError::SyntaxError(0, output_id.to_owned()))?;
debug_assert!(parts.next().is_none());
Ok(output_id)
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
type Response = EnableOutputResponse;
}

View File

@@ -3,14 +3,18 @@ use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, CommandResponse, ResponseParserError, empty_command_request},
common::types::AudioOutputId,
request_tokenizer::RequestTokenizer,
response_tokenizer::{ResponseAttributes, expect_property_type},
};
pub struct Outputs;
empty_command_request!(Outputs, "outputs");
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct OutputsResponse(Vec<Output>);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Output {
pub id: AudioOutputId,
@@ -20,25 +24,16 @@ pub struct Output {
pub attribute: HashMap<String, String>,
}
pub type OutputsResponse = Vec<Output>;
impl Command for Outputs {
type Request = ();
type Response = OutputsResponse;
const COMMAND: &'static str = "outputs";
fn serialize_request(&self, _: Self::Request) -> String {
Self::COMMAND.to_string()
impl CommandResponse<'_> for OutputsResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
debug_assert!(parts.next().is_none());
Ok(())
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
let mut outputs = Vec::new();
let mut id: Option<AudioOutputId> = None;
@@ -114,10 +109,15 @@ impl Command for Outputs {
});
}
Ok(outputs)
Ok(OutputsResponse(outputs))
}
}
impl Command<'_, '_> for Outputs {
type Request = OutputsRequest;
type Response = OutputsResponse;
}
#[cfg(test)]
mod tests {
use indoc::indoc;
@@ -138,10 +138,10 @@ mod tests {
attribute: fifo_path=/tmp/empidee-visualizer.fifo
OK
"};
let result = Outputs::parse_raw_response(input);
let result = Outputs::parse_raw_response(input.as_bytes());
assert_eq!(
result,
Ok(vec![
Ok(OutputsResponse(vec![
Output {
id: 0,
name: "PipeWire Sound Server".to_string(),
@@ -163,7 +163,7 @@ mod tests {
map
},
},
])
])),
);
}
}

View File

@@ -1,10 +1,9 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
common::types::AudioOutputId,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct OutputSet;
@@ -16,22 +15,37 @@ pub struct OutputSetRequest {
pub attribute_value: String,
}
impl Command for OutputSet {
type Request = OutputSetRequest;
type Response = ();
impl CommandRequest<'_> for OutputSetRequest {
const COMMAND: &'static str = "outputset";
fn serialize_request(&self, request: Self::Request) -> String {
fn into_request_enum(self) -> crate::Request {
crate::Request::OutputSet(self.output_id, self.attribute_name, self.attribute_value)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::OutputSet(output_id, attribute_name, attribute_value) => {
Some(OutputSetRequest {
output_id,
attribute_name,
attribute_value,
})
}
_ => None,
}
}
fn serialize(&self) -> String {
format!(
"{} {} {} {}",
Self::COMMAND,
request.output_id,
request.attribute_name,
request.attribute_value
self.output_id,
self.attribute_name,
self.attribute_value
)
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let output_id = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let output_id = output_id
.parse()
@@ -47,11 +61,11 @@ impl Command for OutputSet {
attribute_value: attribute_value.to_string(),
})
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
}
empty_command_response!(OutputSet);
impl Command<'_, '_> for OutputSet {
type Request = OutputSetRequest;
type Response = OutputSetResponse;
}

View File

@@ -1,38 +1,15 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, empty_command_response, single_item_command_request},
common::types::AudioOutputId,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct ToggleOutput;
pub type ToggleOutputRequest = AudioOutputId;
single_item_command_request!(ToggleOutput, "toggleoutput", AudioOutputId);
impl Command for ToggleOutput {
empty_command_response!(ToggleOutput);
impl Command<'_, '_> for ToggleOutput {
type Request = ToggleOutputRequest;
type Response = ();
const COMMAND: &'static str = "toggleoutput";
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {}", Self::COMMAND, request)
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
let output_id = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let output_id = output_id
.parse()
.map_err(|_| RequestParserError::SyntaxError(0, output_id.to_owned()))?;
debug_assert!(parts.next().is_none());
Ok(output_id)
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
type Response = ToggleOutputResponse;
}

View File

@@ -1,11 +1,11 @@
pub mod channels;
pub mod readmessages;
pub mod sendmessage;
pub mod subscribe;
pub mod unsubscribe;
mod channels;
mod readmessages;
mod sendmessage;
mod subscribe;
mod unsubscribe;
pub use channels::Channels;
pub use readmessages::ReadMessages;
pub use sendmessage::SendMessage;
pub use subscribe::Subscribe;
pub use unsubscribe::Unsubscribe;
pub use channels::*;
pub use readmessages::*;
pub use sendmessage::*;
pub use subscribe::*;
pub use unsubscribe::*;

View File

@@ -1,37 +1,30 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, CommandResponse, ResponseParserError, empty_command_request},
common::types::ChannelName,
request_tokenizer::RequestTokenizer,
response_tokenizer::{ResponseAttributes, expect_property_type},
};
pub struct Channels;
empty_command_request!(Channels, "channels");
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ChannelsResponse {
pub channels: Vec<ChannelName>,
}
impl Command for Channels {
type Request = ();
type Response = ChannelsResponse;
const COMMAND: &'static str = "channels";
fn serialize_request(&self, _: Self::Request) -> String {
Self::COMMAND.to_string()
impl<'a> CommandResponse<'a> for ChannelsResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
debug_assert!(parts.next().is_none());
Ok(())
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, 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 {
@@ -49,6 +42,11 @@ impl Command for Channels {
}
}
impl Command<'_, '_> for Channels {
type Request = ChannelsRequest;
type Response = ChannelsResponse;
}
#[cfg(test)]
mod tests {
use super::*;
@@ -63,7 +61,7 @@ mod tests {
channels: baz
OK
"};
let response = Channels::parse_raw_response(response).unwrap();
let response = Channels::parse_raw_response(response.as_bytes()).unwrap();
assert_eq!(
response,
ChannelsResponse {

View File

@@ -1,15 +1,17 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, CommandResponse, ResponseParserError, empty_command_request},
common::types::ChannelName,
request_tokenizer::RequestTokenizer,
response_tokenizer::{ResponseAttributes, expect_property_type},
};
pub struct ReadMessages;
pub type ReadMessagesResponse = Vec<ReadMessagesResponseEntry>;
empty_command_request!(ReadMessages, "readmessages");
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ReadMessagesResponse(Vec<ReadMessagesResponseEntry>);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ReadMessagesResponseEntry {
@@ -17,24 +19,16 @@ pub struct ReadMessagesResponseEntry {
message: String,
}
impl Command for ReadMessages {
type Request = ();
type Response = ReadMessagesResponse;
const COMMAND: &'static str = "readmessages";
fn serialize_request(&self, _: Self::Request) -> String {
Self::COMMAND.to_string()
impl CommandResponse<'_> for ReadMessagesResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
debug_assert!(parts.next().is_none());
Ok(())
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
let parts: Vec<_> = parts.into_vec()?;
debug_assert!(parts.len() % 2 == 0);
@@ -57,10 +51,15 @@ impl Command for ReadMessages {
messages.push(ReadMessagesResponseEntry { channel, message });
}
Ok(messages)
Ok(ReadMessagesResponse(messages))
}
}
impl Command<'_, '_> for ReadMessages {
type Request = ReadMessagesRequest;
type Response = ReadMessagesResponse;
}
#[cfg(test)]
mod tests {
use indoc::indoc;
@@ -76,10 +75,10 @@ mod tests {
message: message2
OK
"};
let result = ReadMessages::parse_raw_response(input);
let result = ReadMessages::parse_raw_response(input.as_bytes());
assert_eq!(
result,
Ok(vec![
Ok(ReadMessagesResponse(vec![
ReadMessagesResponseEntry {
channel: "channel1".parse().unwrap(),
message: "message1".to_string(),
@@ -88,7 +87,7 @@ mod tests {
channel: "channel2".parse().unwrap(),
message: "message2".to_string(),
},
])
]))
);
}
}

View File

@@ -1,10 +1,9 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
common::types::ChannelName,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct SendMessage;
@@ -15,16 +14,27 @@ pub struct SendMessageRequest {
pub message: String,
}
impl Command for SendMessage {
type Request = SendMessageRequest;
type Response = ();
impl CommandRequest<'_> for SendMessageRequest {
const COMMAND: &'static str = "sendmessage";
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {} {}", Self::COMMAND, request.channel, request.message)
fn into_request_enum(self) -> crate::Request {
crate::Request::SendMessage(self.channel, self.message)
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::SendMessage(channel, message) => {
Some(SendMessageRequest { channel, message })
}
_ => None,
}
}
fn serialize(&self) -> String {
format!("{} {} {}", Self::COMMAND, self.channel, self.message)
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let channel = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let channel = channel
.parse()
@@ -37,11 +47,11 @@ impl Command for SendMessage {
Ok(SendMessageRequest { channel, message })
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
}
empty_command_response!(SendMessage);
impl Command<'_, '_> for SendMessage {
type Request = SendMessageRequest;
type Response = SendMessageResponse;
}

View File

@@ -1,36 +1,15 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, empty_command_response, single_item_command_request},
common::types::ChannelName,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct Subscribe;
impl Command for Subscribe {
type Request = ChannelName;
type Response = ();
const COMMAND: &'static str = "subscribe";
single_item_command_request!(Subscribe, "subscribe", ChannelName);
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {}", Self::COMMAND, request)
}
empty_command_response!(Subscribe);
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
let channel_name = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let channel_name = channel_name
.parse()
.map_err(|_| RequestParserError::SyntaxError(0, channel_name.to_owned()))?;
debug_assert!(parts.next().is_none());
Ok(channel_name)
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
impl Command<'_, '_> for Subscribe {
type Request = SubscribeRequest;
type Response = SubscribeResponse;
}

View File

@@ -1,36 +1,15 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, empty_command_response, single_item_command_request},
common::types::ChannelName,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct Unsubscribe;
impl Command for Unsubscribe {
type Request = ChannelName;
type Response = ();
const COMMAND: &'static str = "unsubscribe";
single_item_command_request!(Unsubscribe, "unsubscribe", ChannelName);
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {}", Self::COMMAND, request)
}
empty_command_response!(Unsubscribe);
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
let channel_name = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let channel_name = channel_name
.parse()
.map_err(|_| RequestParserError::SyntaxError(0, channel_name.to_owned()))?;
debug_assert!(parts.next().is_none());
Ok(channel_name)
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
impl Command<'_, '_> for Unsubscribe {
type Request = UnsubscribeRequest;
type Response = UnsubscribeResponse;
}

View File

@@ -1,37 +1,37 @@
pub mod binary_limit;
pub mod close;
pub mod kill;
pub mod password;
pub mod ping;
pub mod protocol;
pub mod protocol_all;
pub mod protocol_available;
pub mod protocol_clear;
pub mod protocol_disable;
pub mod protocol_enable;
pub mod tag_types;
pub mod tag_types_all;
pub mod tag_types_available;
pub mod tag_types_clear;
pub mod tag_types_disable;
pub mod tag_types_enable;
pub mod tag_types_reset;
mod binary_limit;
mod close;
mod kill;
mod password;
mod ping;
mod protocol;
mod protocol_all;
mod protocol_available;
mod protocol_clear;
mod protocol_disable;
mod protocol_enable;
mod tag_types;
mod tag_types_all;
mod tag_types_available;
mod tag_types_clear;
mod tag_types_disable;
mod tag_types_enable;
mod tag_types_reset;
pub use binary_limit::BinaryLimit;
pub use close::Close;
pub use kill::Kill;
pub use password::Password;
pub use ping::Ping;
pub use protocol::Protocol;
pub use protocol_all::ProtocolAll;
pub use protocol_available::ProtocolAvailable;
pub use protocol_clear::ProtocolClear;
pub use protocol_disable::ProtocolDisable;
pub use protocol_enable::ProtocolEnable;
pub use tag_types::TagTypes;
pub use tag_types_all::TagTypesAll;
pub use tag_types_available::TagTypesAvailable;
pub use tag_types_clear::TagTypesClear;
pub use tag_types_disable::TagTypesDisable;
pub use tag_types_enable::TagTypesEnable;
pub use tag_types_reset::TagTypesReset;
pub use binary_limit::*;
pub use close::*;
pub use kill::*;
pub use password::*;
pub use ping::*;
pub use protocol::*;
pub use protocol_all::*;
pub use protocol_available::*;
pub use protocol_clear::*;
pub use protocol_disable::*;
pub use protocol_enable::*;
pub use tag_types::*;
pub use tag_types_all::*;
pub use tag_types_available::*;
pub use tag_types_clear::*;
pub use tag_types_disable::*;
pub use tag_types_enable::*;
pub use tag_types_reset::*;

View File

@@ -1,33 +1,12 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
use crate::commands::{Command, empty_command_response, single_item_command_request};
pub struct BinaryLimit;
impl Command for BinaryLimit {
type Request = u64;
type Response = ();
const COMMAND: &'static str = "binarylimit";
single_item_command_request!(BinaryLimit, "binarylimit", u64);
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {}", Self::COMMAND, request)
}
empty_command_response!(BinaryLimit);
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
let limit = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let limit = limit
.parse()
.map_err(|_| RequestParserError::SyntaxError(0, limit.to_string()))?;
debug_assert!(parts.next().is_none());
Ok(limit)
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
impl Command<'_, '_> for BinaryLimit {
type Request = BinaryLimitRequest;
type Response = BinaryLimitResponse;
}

View File

@@ -1,29 +1,12 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
use crate::commands::{Command, empty_command_request, empty_command_response};
pub struct Close;
impl Command for Close {
type Request = ();
type Response = ();
const COMMAND: &'static str = "close";
empty_command_request!(Close, "close");
fn serialize_request(&self, _request: Self::Request) -> String {
Self::COMMAND.to_string()
}
empty_command_response!(Close);
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
debug_assert!(parts.next().is_none());
Ok(())
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
impl Command<'_, '_> for Close {
type Request = CloseRequest;
type Response = CloseResponse;
}

View File

@@ -1,29 +1,12 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
use crate::commands::{Command, empty_command_request, empty_command_response};
pub struct Kill;
impl Command for Kill {
type Request = ();
type Response = ();
const COMMAND: &'static str = "kill";
empty_command_request!(Kill, "kill");
fn serialize_request(&self, _request: Self::Request) -> String {
Self::COMMAND.to_string()
}
empty_command_response!(Kill);
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
debug_assert!(parts.next().is_none());
Ok(())
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
impl Command<'_, '_> for Kill {
type Request = KillRequest;
type Response = KillResponse;
}

View File

@@ -1,33 +1,12 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
use crate::commands::{Command, empty_command_response, single_item_command_request};
pub struct Password;
impl Command for Password {
type Request = String;
type Response = ();
const COMMAND: &'static str = "password";
single_item_command_request!(Password, "password", String);
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {}", Self::COMMAND, request)
}
empty_command_response!(Password);
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
let password = parts
.next()
.ok_or(RequestParserError::UnexpectedEOF)?
.to_string();
debug_assert!(parts.next().is_none());
Ok(password)
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
impl Command<'_, '_> for Password {
type Request = PasswordRequest;
type Response = PasswordResponse;
}

View File

@@ -1,29 +1,12 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
use crate::commands::{Command, empty_command_request, empty_command_response};
pub struct Ping;
impl Command for Ping {
type Request = ();
type Response = ();
const COMMAND: &'static str = "ping";
empty_command_request!(Ping, "ping");
fn serialize_request(&self, _request: Self::Request) -> String {
Self::COMMAND.to_string()
}
empty_command_response!(Ping);
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
debug_assert!(parts.next().is_none());
Ok(())
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
impl Command<'_, '_> for Ping {
type Request = PingRequest;
type Response = PingResponse;
}

View File

@@ -1,40 +1,15 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
request_tokenizer::RequestTokenizer,
response_tokenizer::{ResponseAttributes, expect_property_type},
commands::{Command, ResponseParserError, empty_command_request, multi_item_command_response},
response_tokenizer::expect_property_type,
};
pub struct Protocol;
pub type ProtocolResponse = Vec<String>;
empty_command_request!(Protocol, "protocol");
impl Command for Protocol {
type Request = ();
multi_item_command_response!(Protocol, "feature", String);
impl Command<'_, '_> for Protocol {
type Request = ProtocolRequest;
type Response = ProtocolResponse;
const COMMAND: &'static str = "protocol";
fn serialize_request(&self, _request: Self::Request) -> String {
Self::COMMAND.to_string()
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
debug_assert!(parts.next().is_none());
Ok(())
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
let parts_: Vec<_> = parts.into_vec()?;
if let Some((k, _)) = parts_.iter().find(|(k, _)| *k != "feature") {
return Err(ResponseParserError::UnexpectedProperty(k));
}
let list = parts_
.into_iter()
.map(|(k, v)| Ok(expect_property_type!(Some(v), k, Text).to_string()))
.collect::<Result<Vec<_>, ResponseParserError>>()?;
Ok(list)
}
}

View File

@@ -1,29 +1,12 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
use crate::commands::{Command, empty_command_request, empty_command_response};
pub struct ProtocolAll;
impl Command for ProtocolAll {
type Request = ();
type Response = ();
const COMMAND: &'static str = "protocol all";
empty_command_request!(ProtocolAll, "protocol all");
fn serialize_request(&self, _request: Self::Request) -> String {
Self::COMMAND.to_string()
}
empty_command_response!(ProtocolAll);
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
debug_assert!(parts.next().is_none());
Ok(())
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
impl Command<'_, '_> for ProtocolAll {
type Request = ProtocolAllRequest;
type Response = ProtocolAllResponse;
}

View File

@@ -1,40 +1,15 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
request_tokenizer::RequestTokenizer,
response_tokenizer::{ResponseAttributes, expect_property_type},
commands::{Command, ResponseParserError, empty_command_request, multi_item_command_response},
response_tokenizer::expect_property_type,
};
pub struct ProtocolAvailable;
pub type ProtocolAvailableResponse = Vec<String>;
empty_command_request!(ProtocolAvailable, "protocol available");
impl Command for ProtocolAvailable {
type Request = ();
multi_item_command_response!(ProtocolAvailable, "feature", String);
impl Command<'_, '_> for ProtocolAvailable {
type Request = ProtocolAvailableRequest;
type Response = ProtocolAvailableResponse;
const COMMAND: &'static str = "protocol available";
fn serialize_request(&self, _request: Self::Request) -> String {
Self::COMMAND.to_string()
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
debug_assert!(parts.next().is_none());
Ok(())
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
let parts_: Vec<_> = parts.into_vec()?;
if let Some((k, _)) = parts_.iter().find(|(k, _)| *k != "feature") {
return Err(ResponseParserError::UnexpectedProperty(k));
}
let list = parts_
.into_iter()
.map(|(k, v)| Ok(expect_property_type!(Some(v), k, Text).to_string()))
.collect::<Result<Vec<_>, ResponseParserError>>()?;
Ok(list)
}
}

View File

@@ -1,29 +1,12 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
use crate::commands::{Command, empty_command_request, empty_command_response};
pub struct ProtocolClear;
impl Command for ProtocolClear {
type Request = ();
type Response = ();
const COMMAND: &'static str = "protocol clear";
empty_command_request!(ProtocolClear, "protocol clear");
fn serialize_request(&self, _request: Self::Request) -> String {
Self::COMMAND.to_string()
}
empty_command_response!(ProtocolClear);
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
debug_assert!(parts.next().is_none());
Ok(())
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
impl Command<'_, '_> for ProtocolClear {
type Request = ProtocolClearRequest;
type Response = ProtocolClearResponse;
}

View File

@@ -1,38 +1,58 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
common::types::Feature,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct ProtocolDisable;
pub type ProtocolDisableRequest = Vec<Feature>;
pub struct ProtocolDisableRequest(Vec<Feature>);
impl Command for ProtocolDisable {
type Request = ProtocolDisableRequest;
type Response = ();
impl CommandRequest<'_> for ProtocolDisableRequest {
const COMMAND: &'static str = "protocol disable";
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {}", Self::COMMAND, request.join(" "))
fn into_request_enum(self) -> crate::Request {
crate::Request::ProtocolDisable(self.0)
}
fn parse_request(parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::ProtocolDisable(features) => Some(ProtocolDisableRequest(features)),
_ => None,
}
}
fn serialize(&self) -> String {
let features = self
.0
.iter()
.map(|f| f.to_string())
.collect::<Vec<String>>()
.join(" ");
format!("{} {}", Self::COMMAND, features)
}
fn parse(parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let mut parts = parts.peekable();
if parts.peek().is_none() {
return Err(RequestParserError::UnexpectedEOF);
}
let features = parts.map(|s| s.to_string()).collect::<Vec<String>>();
let mut features = Vec::new();
for part in parts {
let feature = part
.parse()
.map_err(|_| RequestParserError::SyntaxError(1, part.to_owned()))?;
features.push(feature);
}
Ok(features)
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
Ok(ProtocolDisableRequest(features))
}
}
empty_command_response!(ProtocolDisable);
impl Command<'_, '_> for ProtocolDisable {
type Request = ProtocolDisableRequest;
type Response = ProtocolDisableResponse;
}

View File

@@ -1,38 +1,58 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
common::types::Feature,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct ProtocolEnable;
pub type ProtocolEnableRequest = Vec<Feature>;
pub struct ProtocolEnableRequest(Vec<Feature>);
impl Command for ProtocolEnable {
type Request = ProtocolEnableRequest;
type Response = ();
impl CommandRequest<'_> for ProtocolEnableRequest {
const COMMAND: &'static str = "protocol enable";
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {}", Self::COMMAND, request.join(" "))
fn into_request_enum(self) -> crate::Request {
crate::Request::ProtocolEnable(self.0)
}
fn parse_request(parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::ProtocolEnable(features) => Some(ProtocolEnableRequest(features)),
_ => None,
}
}
fn serialize(&self) -> String {
let features = self
.0
.iter()
.map(|f| f.to_string())
.collect::<Vec<String>>()
.join(" ");
format!("{} {}", Self::COMMAND, features)
}
fn parse(parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let mut parts = parts.peekable();
if parts.peek().is_none() {
return Err(RequestParserError::UnexpectedEOF);
}
let features = parts.map(|s| s.to_string()).collect::<Vec<String>>();
let mut features = Vec::new();
for part in parts {
let feature = part
.parse()
.map_err(|_| RequestParserError::SyntaxError(1, part.to_owned()))?;
features.push(feature);
}
Ok(features)
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
Ok(ProtocolEnableRequest(features))
}
}
empty_command_response!(ProtocolEnable);
impl Command<'_, '_> for ProtocolEnable {
type Request = ProtocolEnableRequest;
type Response = ProtocolEnableResponse;
}

View File

@@ -1,43 +1,15 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
request_tokenizer::RequestTokenizer,
response_tokenizer::{ResponseAttributes, expect_property_type},
commands::{Command, ResponseParserError, empty_command_request, multi_item_command_response},
response_tokenizer::expect_property_type,
};
pub struct TagTypes;
pub type TagTypesResponse = Vec<String>;
empty_command_request!(TagTypes, "tagtypes");
impl Command for TagTypes {
type Request = ();
multi_item_command_response!(TagTypes, "tagtype", String);
impl Command<'_, '_> for TagTypes {
type Request = TagTypesRequest;
type Response = TagTypesResponse;
const COMMAND: &'static str = "tagtypes";
fn serialize_request(&self, _request: Self::Request) -> String {
Self::COMMAND.to_string()
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
debug_assert!(parts.next().is_none());
Ok(())
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
let parts: Vec<_> = parts.into_vec()?;
let mut tagtypes = Vec::with_capacity(parts.len());
for (key, value) in parts.into_iter() {
if key != "tagtype" {
return Err(ResponseParserError::UnexpectedProperty(key));
}
let tagtype = expect_property_type!(Some(value), "tagtype", Text).to_string();
tagtypes.push(tagtype);
}
Ok(tagtypes)
}
}

View File

@@ -1,29 +1,12 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
use crate::commands::{Command, empty_command_request, empty_command_response};
pub struct TagTypesAll;
impl Command for TagTypesAll {
type Request = ();
type Response = ();
const COMMAND: &'static str = "tagtypes all";
empty_command_request!(TagTypesAll, "tagtypes all");
fn serialize_request(&self, _request: Self::Request) -> String {
Self::COMMAND.to_string()
}
empty_command_response!(TagTypesAll);
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
debug_assert!(parts.next().is_none());
Ok(())
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
impl Command<'_, '_> for TagTypesAll {
type Request = TagTypesAllRequest;
type Response = TagTypesAllResponse;
}

View File

@@ -1,43 +1,15 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
request_tokenizer::RequestTokenizer,
response_tokenizer::{ResponseAttributes, expect_property_type},
commands::{Command, ResponseParserError, empty_command_request, multi_item_command_response},
response_tokenizer::expect_property_type,
};
pub struct TagTypesAvailable;
pub type TagTypesAvailableResponse = Vec<String>;
empty_command_request!(TagTypesAvailable, "tagtypes available");
impl Command for TagTypesAvailable {
type Request = ();
multi_item_command_response!(TagTypesAvailable, "tagtype", String);
impl Command<'_, '_> for TagTypesAvailable {
type Request = TagTypesAvailableRequest;
type Response = TagTypesAvailableResponse;
const COMMAND: &'static str = "tagtypes available";
fn serialize_request(&self, _request: Self::Request) -> String {
Self::COMMAND.to_string()
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
debug_assert!(parts.next().is_none());
Ok(())
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
let parts: Vec<_> = parts.into_vec()?;
let mut tagtypes = Vec::with_capacity(parts.len());
for (key, value) in parts.into_iter() {
if key != "tagtype" {
return Err(ResponseParserError::UnexpectedProperty(key));
}
let tagtype = expect_property_type!(Some(value), "tagtype", Text).to_string();
tagtypes.push(tagtype);
}
Ok(tagtypes)
}
}

View File

@@ -1,29 +1,12 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
use crate::commands::{Command, empty_command_request, empty_command_response};
pub struct TagTypesClear;
impl Command for TagTypesClear {
type Request = ();
type Response = ();
const COMMAND: &'static str = "tagtypes clear";
empty_command_request!(TagTypesClear, "tagtypes clear");
fn serialize_request(&self, _request: Self::Request) -> String {
Self::COMMAND.to_string()
}
empty_command_response!(TagTypesClear);
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
debug_assert!(parts.next().is_none());
Ok(())
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
impl Command<'_, '_> for TagTypesClear {
type Request = TagTypesClearRequest;
type Response = TagTypesClearResponse;
}

View File

@@ -1,38 +1,59 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
common::types::TagName,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct TagTypesDisable;
pub type TagTypesDisableRequest = Vec<TagName>;
pub struct TagTypesDisableRequest(Vec<TagName>);
impl Command for TagTypesDisable {
type Request = TagTypesDisableRequest;
type Response = ();
impl CommandRequest<'_> for TagTypesDisableRequest {
const COMMAND: &'static str = "tagtypes disable";
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {}", Self::COMMAND, request.join(" "))
fn into_request_enum(self) -> crate::Request {
crate::Request::TagTypesDisable(self.0)
}
fn parse_request(parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::TagTypesDisable(req) => Some(TagTypesDisableRequest(req)),
_ => None,
}
}
fn serialize(&self) -> String {
format!(
"{} {}",
Self::COMMAND,
self.0
.iter()
.map(|tag| tag.as_str())
.collect::<Vec<&str>>()
.join(" ")
)
}
fn parse(parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let mut parts = parts.peekable();
if parts.peek().is_none() {
return Err(RequestParserError::UnexpectedEOF);
}
let tag_types = parts.map(|s| s.to_string()).collect::<Vec<String>>();
let tag_types = parts
.map(|s| {
s.parse()
.map_err(|_| RequestParserError::SyntaxError(1, s.to_owned()))
})
.collect::<Result<Vec<TagName>, RequestParserError>>()?;
Ok(tag_types)
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
Ok(TagTypesDisableRequest(tag_types))
}
}
empty_command_response!(TagTypesDisable);
impl Command<'_, '_> for TagTypesDisable {
type Request = TagTypesDisableRequest;
type Response = TagTypesDisableResponse;
}

View File

@@ -1,38 +1,59 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
common::types::TagName,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct TagTypesEnable;
pub type TagTypesEnableRequest = Vec<TagName>;
pub struct TagTypesEnableRequest(Vec<TagName>);
impl Command for TagTypesEnable {
type Request = TagTypesEnableRequest;
type Response = ();
impl CommandRequest<'_> for TagTypesEnableRequest {
const COMMAND: &'static str = "tagtypes enable";
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {}", Self::COMMAND, request.join(" "))
fn into_request_enum(self) -> crate::Request {
crate::Request::TagTypesEnable(self.0)
}
fn parse_request(parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::TagTypesEnable(req) => Some(TagTypesEnableRequest(req)),
_ => None,
}
}
fn serialize(&self) -> String {
format!(
"{} {}",
Self::COMMAND,
self.0
.iter()
.map(|tag| tag.as_str())
.collect::<Vec<&str>>()
.join(" ")
)
}
fn parse(parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let mut parts = parts.peekable();
if parts.peek().is_none() {
return Err(RequestParserError::UnexpectedEOF);
}
let tag_types = parts.map(|s| s.to_string()).collect::<Vec<String>>();
let tag_types = parts
.map(|s| {
s.parse()
.map_err(|_| RequestParserError::SyntaxError(1, s.to_owned()))
})
.collect::<Result<Vec<TagName>, RequestParserError>>()?;
Ok(tag_types)
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
Ok(TagTypesEnableRequest(tag_types))
}
}
empty_command_response!(TagTypesEnable);
impl Command<'_, '_> for TagTypesEnable {
type Request = TagTypesEnableRequest;
type Response = TagTypesEnableResponse;
}

View File

@@ -1,39 +1,59 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
common::types::TagName,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct TagTypesReset;
pub type TagTypesResetRequest = Vec<TagName>;
pub struct TagTypesResetRequest(Vec<TagName>);
impl Command for TagTypesReset {
type Request = TagTypesResetRequest;
type Response = ();
impl CommandRequest<'_> for TagTypesResetRequest {
const COMMAND: &'static str = "tagtypes reset";
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {}", Self::COMMAND, request.join(" "))
fn into_request_enum(self) -> crate::Request {
crate::Request::TagTypesReset(self.0)
}
fn parse_request(parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::TagTypesReset(req) => Some(TagTypesResetRequest(req)),
_ => None,
}
}
fn serialize(&self) -> String {
format!(
"{} {}",
Self::COMMAND,
self.0
.iter()
.map(|tag| tag.as_str())
.collect::<Vec<&str>>()
.join(" ")
)
}
fn parse(parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let mut parts = parts.peekable();
if parts.peek().is_none() {
return Err(RequestParserError::UnexpectedEOF);
}
// TODO: verify that the tag types are split by whitespace
let tag_types = parts.map(|s| s.to_string()).collect::<Vec<String>>();
let tag_types = parts
.map(|s| {
s.parse()
.map_err(|_| RequestParserError::SyntaxError(1, s.to_owned()))
})
.collect::<Result<Vec<TagName>, RequestParserError>>()?;
Ok(tag_types)
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
Ok(TagTypesResetRequest(tag_types))
}
}
empty_command_response!(TagTypesReset);
impl Command<'_, '_> for TagTypesReset {
type Request = TagTypesResetRequest;
type Response = TagTypesResetResponse;
}

View File

@@ -1,19 +1,19 @@
pub mod next;
pub mod pause;
pub mod play;
pub mod playid;
pub mod previous;
pub mod seek;
pub mod seekcur;
pub mod seekid;
pub mod stop;
mod next;
mod pause;
mod play;
mod playid;
mod previous;
mod seek;
mod seekcur;
mod seekid;
mod stop;
pub use next::Next;
pub use pause::Pause;
pub use play::Play;
pub use playid::PlayId;
pub use previous::Previous;
pub use seek::Seek;
pub use seekcur::SeekCur;
pub use seekid::SeekId;
pub use stop::Stop;
pub use next::*;
pub use pause::*;
pub use play::*;
pub use playid::*;
pub use previous::*;
pub use seek::*;
pub use seekcur::*;
pub use seekid::*;
pub use stop::*;

View File

@@ -1,29 +1,12 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
use crate::commands::{Command, empty_command_request, empty_command_response};
pub struct Next;
impl Command for Next {
type Request = ();
type Response = ();
const COMMAND: &'static str = "next";
empty_command_request!(Next, "next");
fn serialize_request(&self, _request: Self::Request) -> String {
Self::COMMAND.to_string()
}
empty_command_response!(Next);
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
debug_assert!(parts.next().is_none());
Ok(())
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
impl Command<'_, '_> for Next {
type Request = NextRequest;
type Response = NextResponse;
}

View File

@@ -1,25 +1,35 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct Pause;
impl Command for Pause {
type Request = Option<bool>;
type Response = ();
pub struct PauseRequest(Option<bool>);
impl CommandRequest<'_> for PauseRequest {
const COMMAND: &'static str = "pause";
fn serialize_request(&self, request: Self::Request) -> String {
fn into_request_enum(self) -> crate::Request {
crate::Request::Pause(self.0)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::Pause(value) => Some(PauseRequest(value)),
_ => None,
}
}
fn serialize(&self) -> String {
match self.0 {
Some(true) => format!("{} 1", Self::COMMAND),
Some(false) => format!("{} 0", Self::COMMAND),
None => Self::COMMAND.to_string(),
}
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let result = match parts.next() {
Some("0") => Ok(Some(false)),
Some("1") => Ok(Some(true)),
@@ -29,13 +39,13 @@ impl Command for Pause {
debug_assert!(parts.next().is_none());
result
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
result.map(PauseRequest)
}
}
empty_command_response!(Pause);
impl Command<'_, '_> for Pause {
type Request = PauseRequest;
type Response = PauseResponse;
}

View File

@@ -1,38 +1,15 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, empty_command_response, single_item_command_request},
common::types::SongPosition,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct Play;
impl Command for Play {
type Request = SongPosition;
type Response = ();
const COMMAND: &'static str = "play";
single_item_command_request!(Play, "play", SongPosition);
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {}", Self::COMMAND, request)
}
empty_command_response!(Play);
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
let songpos = match parts.next() {
Some(s) => s
.parse::<SongPosition>()
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
debug_assert!(parts.next().is_none());
Ok(songpos)
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
impl Command<'_, '_> for Play {
type Request = PlayRequest;
type Response = PlayResponse;
}

View File

@@ -1,38 +1,15 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, empty_command_response, single_item_command_request},
common::types::SongId,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct PlayId;
impl Command for PlayId {
type Request = SongId;
type Response = ();
const COMMAND: &'static str = "playid";
single_item_command_request!(PlayId, "playid", SongId);
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {}", Self::COMMAND, request)
}
empty_command_response!(PlayId);
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
let songid = match parts.next() {
Some(s) => s
.parse::<SongId>()
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
debug_assert!(parts.next().is_none());
Ok(songid)
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
impl Command<'_, '_> for PlayId {
type Request = PlayIdRequest;
type Response = PlayIdResponse;
}

View File

@@ -1,29 +1,12 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
use crate::commands::{Command, empty_command_request, empty_command_response};
pub struct Previous;
impl Command for Previous {
type Request = ();
type Response = ();
const COMMAND: &'static str = "previous";
empty_command_request!(Previous, "previous");
fn serialize_request(&self, _request: Self::Request) -> String {
Self::COMMAND.to_string()
}
empty_command_response!(Previous);
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
debug_assert!(parts.next().is_none());
Ok(())
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
impl Command<'_, '_> for Previous {
type Request = PreviousRequest;
type Response = PreviousResponse;
}

View File

@@ -1,10 +1,9 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
common::types::{SongPosition, TimeWithFractions},
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct Seek;
@@ -15,16 +14,25 @@ pub struct SeekRequest {
pub time: TimeWithFractions,
}
impl Command for Seek {
type Request = SeekRequest;
type Response = ();
impl CommandRequest<'_> for SeekRequest {
const COMMAND: &'static str = "seek";
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {} {}", Self::COMMAND, request.songpos, request.time)
fn into_request_enum(self) -> crate::Request {
crate::Request::Seek(self.songpos, self.time)
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::Seek(songpos, time) => Some(SeekRequest { songpos, time }),
_ => None,
}
}
fn serialize(&self) -> String {
format!("{} {} {}", Self::COMMAND, self.songpos, self.time)
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let songpos = match parts.next() {
Some(s) => s
.parse::<SongPosition>()
@@ -43,11 +51,11 @@ impl Command for Seek {
Ok(SeekRequest { songpos, time })
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
}
empty_command_response!(Seek);
impl Command<'_, '_> for Seek {
type Request = SeekRequest;
type Response = SeekResponse;
}

View File

@@ -1,7 +1,7 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, RequestParserError, ResponseAttributes, ResponseParserError},
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
common::types::{SeekMode, TimeWithFractions},
request_tokenizer::RequestTokenizer,
};
@@ -14,23 +14,32 @@ pub struct SeekCurRequest {
pub time: TimeWithFractions,
}
impl Command for SeekCur {
type Request = SeekCurRequest;
type Response = ();
impl CommandRequest<'_> for SeekCurRequest {
const COMMAND: &'static str = "seekcur";
fn serialize_request(&self, request: Self::Request) -> String {
let time_str = match request.mode {
SeekMode::Absolute => format!("{}", request.time),
SeekMode::Relative if request.time >= 0.0 => format!("+{}", request.time),
SeekMode::Relative => format!("-{}", -request.time),
fn into_request_enum(self) -> crate::Request {
crate::Request::SeekCur(self.mode, self.time)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::SeekCur(mode, time) => Some(SeekCurRequest { mode, time }),
_ => None,
}
}
fn serialize(&self) -> String {
let time_str = match self.mode {
SeekMode::Absolute => format!("{}", self.time),
SeekMode::Relative if self.time >= 0.0 => format!("+{}", self.time),
SeekMode::Relative => format!("-{}", -self.time),
SeekMode::RelativeReverse => unimplemented!(), // TODO: should this happen?
};
format!("{} {}", Self::COMMAND, time_str)
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let time_raw = match parts.next() {
Some(t) => t,
None => return Err(RequestParserError::UnexpectedEOF),
@@ -61,11 +70,11 @@ impl Command for SeekCur {
Ok(SeekCurRequest { mode, time })
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
}
empty_command_response!(SeekCur);
impl Command<'_, '_> for SeekCur {
type Request = SeekCurRequest;
type Response = SeekCurResponse;
}

View File

@@ -1,10 +1,9 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
common::types::{SongId, TimeWithFractions},
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct SeekId;
@@ -15,16 +14,25 @@ pub struct SeekIdRequest {
pub time: TimeWithFractions,
}
impl Command for SeekId {
type Request = SeekIdRequest;
type Response = ();
impl CommandRequest<'_> for SeekIdRequest {
const COMMAND: &'static str = "seekid";
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {} {}", Self::COMMAND, request.songid, request.time)
fn into_request_enum(self) -> crate::Request {
crate::Request::SeekId(self.songid, self.time)
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::SeekId(songid, time) => Some(SeekIdRequest { songid, time }),
_ => None,
}
}
fn serialize(&self) -> String {
format!("{} {} {}", Self::COMMAND, self.songid, self.time)
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let songid = match parts.next() {
Some(s) => s
.parse::<SongId>()
@@ -43,11 +51,11 @@ impl Command for SeekId {
Ok(SeekIdRequest { songid, time })
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
}
empty_command_response!(SeekId);
impl Command<'_, '_> for SeekId {
type Request = SeekIdRequest;
type Response = SeekIdResponse;
}

View File

@@ -1,29 +1,12 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
use crate::commands::{Command, empty_command_request, empty_command_response};
pub struct Stop;
impl Command for Stop {
type Request = ();
type Response = ();
const COMMAND: &'static str = "stop";
empty_command_request!(Stop, "stop");
fn serialize_request(&self, _request: Self::Request) -> String {
Self::COMMAND.to_string()
}
empty_command_response!(Stop);
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
debug_assert!(parts.next().is_none());
Ok(())
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
impl Command<'_, '_> for Stop {
type Request = StopRequest;
type Response = StopResponse;
}

View File

@@ -1,9 +1,9 @@
pub mod listmounts;
pub mod listneighbors;
pub mod mount;
pub mod unmount;
mod listmounts;
mod listneighbors;
mod mount;
mod unmount;
pub use listmounts::ListMounts;
pub use listneighbors::ListNeighbors;
pub use mount::Mount;
pub use unmount::Unmount;
pub use listmounts::*;
pub use listneighbors::*;
pub use mount::*;
pub use unmount::*;

View File

@@ -1,39 +1,17 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
request_tokenizer::RequestTokenizer,
response_tokenizer::{ResponseAttributes, expect_property_type},
commands::{Command, ResponseParserError, empty_command_request, multi_item_command_response},
response_tokenizer::expect_property_type,
};
pub struct ListMounts;
impl Command for ListMounts {
type Request = ();
type Response = Vec<String>;
const COMMAND: &'static str = "listmounts";
empty_command_request!(ListMounts, "listmounts");
fn serialize_request(&self, _request: Self::Request) -> String {
Self::COMMAND.to_string()
}
multi_item_command_response!(ListMounts, "mount", String);
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
debug_assert!(parts.next().is_none());
Ok(())
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
let parts: Vec<_> = parts.into_vec()?;
let mut result = Vec::with_capacity(parts.len());
for (key, value) in parts.into_iter() {
if key != "mount" {
return Err(ResponseParserError::UnexpectedProperty(key));
}
let value = expect_property_type!(Some(value), "mount", Text).to_string();
result.push(value);
}
Ok(result)
}
impl Command<'_, '_> for ListMounts {
type Request = ListMountsRequest;
type Response = ListMountsResponse;
}
#[cfg(test)]
@@ -49,7 +27,13 @@ mod tests {
mount: /mnt/music
OK
"};
let result = ListMounts::parse_raw_response(input);
assert_eq!(result, Ok(vec!["".to_string(), "/mnt/music".to_string(),]));
let result = ListMounts::parse_raw_response(input.as_bytes());
assert_eq!(
result,
Ok(ListMountsResponse(vec![
"".to_string(),
"/mnt/music".to_string(),
]))
);
}
}

View File

@@ -1,32 +1,29 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
request_tokenizer::RequestTokenizer,
commands::{Command, CommandResponse, ResponseParserError, empty_command_request},
response_tokenizer::{ResponseAttributes, expect_property_type},
};
pub struct ListNeighbors;
pub type ListNeighborsResponse = HashMap<String, String>;
empty_command_request!(ListNeighbors, "listneighbors");
impl Command for ListNeighbors {
type Request = ();
type Response = ListNeighborsResponse;
const COMMAND: &'static str = "listneighbors";
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ListNeighborsResponse(HashMap<String, String>);
fn serialize_request(&self, _request: Self::Request) -> String {
Self::COMMAND.to_string()
impl CommandResponse<'_> for ListNeighborsResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
debug_assert!(parts.next().is_none());
Ok(())
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
let parts: Vec<_> = parts.into_vec()?;
debug_assert!(parts.len() % 2 == 0);
@@ -45,6 +42,11 @@ impl Command for ListNeighbors {
result.insert(neighbor, name);
}
Ok(result)
Ok(ListNeighborsResponse(result))
}
}
impl Command<'_, '_> for ListNeighbors {
type Request = ListNeighborsRequest;
type Response = ListNeighborsResponse;
}

View File

@@ -1,29 +1,44 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
common::types::{MountPath, Uri},
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct Mount;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MountRequest {
pub path: String,
pub uri: String,
pub path: MountPath,
pub uri: Uri,
}
impl Command for Mount {
type Request = MountRequest;
type Response = ();
impl CommandRequest<'_> for MountRequest {
const COMMAND: &'static str = "mount";
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {} {}", Self::COMMAND, request.path, request.uri)
fn into_request_enum(self) -> crate::Request {
crate::Request::Mount(self.path, self.uri)
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::Mount(path, uri) => Some(MountRequest { path, uri }),
_ => None,
}
}
fn serialize(&self) -> String {
debug_assert!(self.path.to_str().is_some());
format!(
"{} {} {}",
Self::COMMAND,
self.path.to_str().unwrap_or("<invalid path>"),
self.uri
)
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let path = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let path = path
.parse()
@@ -38,11 +53,11 @@ impl Command for Mount {
Ok(MountRequest { path, uri })
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
}
empty_command_response!(Mount);
impl Command<'_, '_> for Mount {
type Request = MountRequest;
type Response = MountResponse;
}

View File

@@ -1,37 +1,56 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
MountPath,
commands::{
Command, CommandRequest, RequestParserError, RequestTokenizer, empty_command_response,
},
};
pub struct Unmount;
pub type UnmountRequest = String;
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct UnmountRequest(MountPath);
impl Command for Unmount {
type Request = UnmountRequest;
type Response = ();
impl CommandRequest<'_> for UnmountRequest {
const COMMAND: &'static str = "unmount";
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {}", Self::COMMAND, request)
fn into_request_enum(self) -> crate::Request {
match Self::COMMAND {
"unmount" => crate::Request::Unmount(self.0),
_ => unimplemented!(),
}
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
let path = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let path = path
.parse()
.map_err(|_| RequestParserError::SyntaxError(0, path.to_string()))?;
fn from_request_enum(request: crate::Request) -> Option<Self> {
match (Self::COMMAND, request) {
("unmount", crate::Request::Unmount(item)) => Some(UnmountRequest(item)),
_ => None,
}
}
fn serialize(&self) -> String {
debug_assert!(self.0.to_str().is_some());
format!(
"{} {}",
Self::COMMAND,
self.0.to_str().unwrap_or("<invalid path>")
)
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let item_token = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let item = item_token
.parse::<MountPath>()
.map_err(|_| RequestParserError::SyntaxError(0, item_token.to_owned()))?;
debug_assert!(parts.next().is_none());
Ok(path)
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
Ok(UnmountRequest(item))
}
}
empty_command_response!(Unmount);
impl Command<'_, '_> for Unmount {
type Request = UnmountRequest;
type Response = UnmountResponse;
}

View File

@@ -1,37 +1,37 @@
pub mod albumart;
pub mod count;
pub mod find;
pub mod findadd;
pub mod getfingerprint;
pub mod list;
pub mod listall;
pub mod listallinfo;
pub mod listfiles;
pub mod lsinfo;
pub mod readcomments;
pub mod readpicture;
pub mod rescan;
pub mod search;
pub mod searchadd;
pub mod searchaddpl;
pub mod searchcount;
pub mod update;
mod albumart;
mod count;
mod find;
mod findadd;
mod getfingerprint;
mod list;
mod listall;
mod listallinfo;
mod listfiles;
mod lsinfo;
mod readcomments;
mod readpicture;
mod rescan;
mod search;
mod searchadd;
mod searchaddpl;
mod searchcount;
mod update;
pub use albumart::AlbumArt;
pub use count::Count;
pub use find::Find;
pub use findadd::FindAdd;
pub use getfingerprint::GetFingerprint;
pub use list::List;
pub use listall::ListAll;
pub use listallinfo::ListAllInfo;
pub use listfiles::ListFiles;
pub use lsinfo::LsInfo;
pub use readcomments::ReadComments;
pub use readpicture::ReadPicture;
pub use rescan::Rescan;
pub use search::Search;
pub use searchadd::SearchAdd;
pub use searchaddpl::SearchAddPl;
pub use searchcount::SearchCount;
pub use update::Update;
pub use albumart::*;
pub use count::*;
pub use find::*;
pub use findadd::*;
pub use getfingerprint::*;
pub use list::*;
pub use listall::*;
pub use listallinfo::*;
pub use listfiles::*;
pub use lsinfo::*;
pub use readcomments::*;
pub use readpicture::*;
pub use rescan::*;
pub use search::*;
pub use searchadd::*;
pub use searchaddpl::*;
pub use searchcount::*;
pub use update::*;

View File

@@ -3,7 +3,7 @@ use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError},
common::types::{Offset, Uri},
request_tokenizer::RequestTokenizer,
response_tokenizer::{ResponseAttributes, get_and_parse_property, get_property},
@@ -17,22 +17,25 @@ pub struct AlbumArtRequest {
offset: Offset,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AlbumArtResponse {
pub size: usize,
pub binary: Vec<u8>,
}
impl Command for AlbumArt {
type Request = AlbumArtRequest;
type Response = AlbumArtResponse;
impl CommandRequest<'_> for AlbumArtRequest {
const COMMAND: &'static str = "albumart";
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {} {}", Self::COMMAND, request.uri, request.offset)
fn into_request_enum(self) -> crate::Request {
crate::Request::AlbumArt(self.uri, self.offset)
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::AlbumArt(uri, offset) => Some(AlbumArtRequest { uri, offset }),
_ => None,
}
}
fn serialize(&self) -> String {
format!("{} {} {}", Self::COMMAND, self.uri, self.offset)
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let uri = match parts.next() {
Some(s) => s,
None => return Err(RequestParserError::UnexpectedEOF),
@@ -52,10 +55,24 @@ impl Command for AlbumArt {
offset,
})
}
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AlbumArtResponse {
pub size: usize,
pub binary: Vec<u8>,
}
impl CommandResponse<'_> for AlbumArtResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
let parts: HashMap<_, _> = parts.into_map()?;
let size = get_and_parse_property!(parts, "size", Text);
@@ -65,3 +82,8 @@ impl Command for AlbumArt {
Ok(AlbumArtResponse { size, binary })
}
}
impl Command<'_, '_> for AlbumArt {
type Request = AlbumArtRequest;
type Response = AlbumArtResponse;
}

View File

@@ -3,7 +3,7 @@ use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError},
common::types::GroupType,
filter::Filter,
request_tokenizer::RequestTokenizer,
@@ -18,26 +18,29 @@ pub struct CountRequest {
group: Option<GroupType>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CountResponse {
pub songs: usize,
pub playtime: u64,
}
impl Command for Count {
type Request = CountRequest;
type Response = CountResponse;
impl CommandRequest<'_> for CountRequest {
const COMMAND: &'static str = "count";
fn serialize_request(&self, request: Self::Request) -> String {
let mut cmd = format!("{} {}", Self::COMMAND, request.filter);
if let Some(group) = request.group {
fn into_request_enum(self) -> crate::Request {
crate::Request::Count(self.filter, self.group)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::Count(filter, group) => Some(CountRequest { filter, group }),
_ => None,
}
}
fn serialize(&self) -> String {
let mut cmd = format!("{} {}", Self::COMMAND, self.filter);
if let Some(group) = self.group.as_ref() {
cmd.push_str(&format!(" group {}", group));
}
cmd
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let filter = match parts.next() {
Some(f) => {
Filter::parse(f).map_err(|_| RequestParserError::SyntaxError(1, f.to_owned()))?
@@ -61,10 +64,24 @@ impl Command for Count {
Ok(CountRequest { filter, group })
}
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CountResponse {
pub songs: usize,
pub playtime: u64,
}
impl CommandResponse<'_> for CountResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
let parts: HashMap<_, _> = parts.into_map()?;
let songs = get_and_parse_property!(parts, "songs", Text);
@@ -73,3 +90,8 @@ impl Command for Count {
Ok(CountResponse { songs, playtime })
}
}
impl Command<'_, '_> for Count {
type Request = CountRequest;
type Response = CountResponse;
}

View File

@@ -1,9 +1,8 @@
use serde::{Deserialize, Serialize};
use crate::{
DbSelectionPrintResponse, DbSongInfo,
commands::{Command, RequestParserError, ResponseParserError},
common::types::{Sort, WindowRange},
commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError},
common::types::{DbSelectionPrintResponse, DbSongInfo, Sort, WindowRange},
filter::Filter,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
@@ -18,26 +17,36 @@ pub struct FindRequest {
window: Option<WindowRange>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FindResponse(Vec<DbSongInfo>);
impl Command for Find {
type Request = FindRequest;
type Response = FindResponse;
impl CommandRequest<'_> for FindRequest {
const COMMAND: &'static str = "find";
fn serialize_request(&self, request: Self::Request) -> String {
let mut cmd = format!("{} {}", Self::COMMAND, request.filter);
if let Some(sort) = request.sort {
fn into_request_enum(self) -> crate::Request {
crate::Request::Find(self.filter, self.sort, self.window)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::Find(filter, sort, window) => Some(FindRequest {
filter,
sort,
window,
}),
_ => None,
}
}
fn serialize(&self) -> String {
let mut cmd = format!("{} {}", Self::COMMAND, self.filter);
if let Some(sort) = &self.sort {
cmd.push_str(&format!(" sort {}", sort));
}
if let Some(window) = request.window {
if let Some(window) = &self.window {
cmd.push_str(&format!(" window {}", window));
}
cmd
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let filter = match parts.next() {
Some(f) => {
Filter::parse(f).map_err(|_| RequestParserError::SyntaxError(1, f.to_owned()))?
@@ -73,10 +82,21 @@ impl Command for Find {
window,
})
}
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FindResponse(Vec<DbSongInfo>);
impl CommandResponse<'_> for FindResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
DbSelectionPrintResponse::parse(parts)?
.into_iter()
.map(|i| match i {
@@ -92,3 +112,8 @@ impl Command for Find {
.map(FindResponse)
}
}
impl Command<'_, '_> for Find {
type Request = FindRequest;
type Response = FindResponse;
}

View File

@@ -1,11 +1,10 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
common::types::{SongPosition, Sort, WindowRange},
filter::Filter,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct FindAdd;
@@ -18,26 +17,40 @@ pub struct FindAddRequest {
position: Option<SongPosition>,
}
impl Command for FindAdd {
type Request = FindAddRequest;
type Response = ();
impl CommandRequest<'_> for FindAddRequest {
const COMMAND: &'static str = "findadd";
fn serialize_request(&self, request: Self::Request) -> String {
let mut cmd = format!("{} {}", Self::COMMAND, request.filter);
if let Some(sort) = request.sort {
fn into_request_enum(self) -> crate::Request {
crate::Request::FindAdd(self.filter, self.sort, self.window, self.position)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::FindAdd(filter, sort, window, position) => Some(FindAddRequest {
filter,
sort,
window,
position,
}),
_ => None,
}
}
fn serialize(&self) -> String {
let mut cmd = format!("{} {}", Self::COMMAND, self.filter);
if let Some(sort) = &self.sort {
cmd.push_str(&format!(" sort {}", sort));
}
if let Some(window) = request.window {
if let Some(window) = &self.window {
cmd.push_str(&format!(" window {}", window));
}
if let Some(position) = request.position {
if let Some(position) = &self.position {
cmd.push_str(&format!(" position {}", position));
}
cmd
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let filter = match parts.next() {
Some(f) => {
Filter::parse(f).map_err(|_| RequestParserError::SyntaxError(1, f.to_owned()))?
@@ -84,11 +97,11 @@ impl Command for FindAdd {
position,
})
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
}
empty_command_response!(FindAdd);
impl Command<'_, '_> for FindAdd {
type Request = FindAddRequest;
type Response = FindAddResponse;
}

View File

@@ -3,42 +3,30 @@ use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, CommandResponse, ResponseParserError, single_item_command_request},
common::types::Uri,
request_tokenizer::RequestTokenizer,
response_tokenizer::{ResponseAttributes, get_and_parse_property},
};
pub struct GetFingerprint;
single_item_command_request!(GetFingerprint, "getfingerprint", Uri);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct GetFingerprintResponse {
pub chromaprint: String,
}
impl Command for GetFingerprint {
type Request = Uri;
type Response = GetFingerprintResponse;
const COMMAND: &'static str = "getfingerprint";
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {}", Self::COMMAND, request)
impl CommandResponse<'_> for GetFingerprintResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
let uri = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let uri = uri
.parse()
.map_err(|_| RequestParserError::SyntaxError(1, uri.to_owned()))?;
debug_assert!(parts.next().is_none());
Ok(uri)
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
let parts: HashMap<_, _> = parts.into_map()?;
let chromaprint = get_and_parse_property!(parts, "chromaprint", Text);
@@ -46,3 +34,8 @@ impl Command for GetFingerprint {
Ok(GetFingerprintResponse { chromaprint })
}
}
impl Command<'_, '_> for GetFingerprint {
type Request = GetFingerprintRequest;
type Response = GetFingerprintResponse;
}

View File

@@ -1,7 +1,7 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError},
common::types::{GroupType, TagName, WindowRange},
filter::Filter,
request_tokenizer::RequestTokenizer,
@@ -18,28 +18,40 @@ pub struct ListRequest {
window: Option<WindowRange>,
}
pub type ListResponse = Vec<String>;
impl Command for List {
type Request = ListRequest;
type Response = ListResponse;
impl CommandRequest<'_> for ListRequest {
const COMMAND: &'static str = "list";
fn serialize_request(&self, request: Self::Request) -> String {
let mut cmd = match &request.filter {
Some(f) => format!("{} {} {}", Self::COMMAND, request.tagname, f),
None => format!("{} {}", Self::COMMAND, request.tagname),
fn into_request_enum(self) -> crate::Request {
crate::Request::List(self.tagname, self.filter, self.groups, self.window)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::List(tagname, filter, groups, window) => Some(ListRequest {
tagname,
filter,
groups,
window,
}),
_ => None,
}
}
fn serialize(&self) -> String {
let mut cmd = match &self.filter {
Some(f) => format!("{} {} {}", Self::COMMAND, self.tagname, f),
None => format!("{} {}", Self::COMMAND, self.tagname),
};
for group in request.groups {
for group in &self.groups {
cmd.push_str(&format!(" group {}", group));
}
if let Some(window) = request.window {
if let Some(window) = &self.window {
cmd.push_str(&format!(" window {}", window));
}
cmd
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let tagname = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let tagname = tagname
.parse()
@@ -91,10 +103,21 @@ impl Command for List {
window,
})
}
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ListResponse(Vec<String>);
impl CommandResponse<'_> for ListResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
let parts_: Vec<_> = parts.into_vec()?;
debug_assert!({
let key = parts_.first().map(|(k, _)| k);
@@ -106,6 +129,11 @@ impl Command for List {
.map(|(k, v)| Ok(expect_property_type!(Some(v), k, Text).to_string()))
.collect::<Result<Vec<_>, ResponseParserError>>()?;
Ok(list)
Ok(ListResponse(list))
}
}
impl Command<'_, '_> for List {
type Request = ListRequest;
type Response = ListResponse;
}

View File

@@ -1,48 +1,42 @@
use serde::{Deserialize, Serialize};
use crate::{
DbSelectionPrintResponse,
commands::{Command, RequestParserError, ResponseParserError},
commands::{
Command, CommandResponse, RequestParserError, ResponseParserError,
single_optional_item_command_request,
},
common::types::Uri,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct ListAll;
single_optional_item_command_request!(ListAll, "listall", Uri);
// TODO: This is supposed to be a tree-like structure, with directories containing files and playlists
pub type ListAllResponse = Vec<DbSelectionPrintResponse>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ListAllResponse(Vec<DbSelectionPrintResponse>);
impl Command for ListAll {
type Request = Option<Uri>;
impl CommandResponse<'_> for ListAllResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
let result = DbSelectionPrintResponse::parse(parts)?;
Ok(ListAllResponse(result))
}
}
impl Command<'_, '_> for ListAll {
type Request = ListAllRequest;
type Response = ListAllResponse;
const COMMAND: &'static str = "listall";
fn serialize_request(&self, request: Self::Request) -> String {
if let Some(uri) = request {
format!("{} {}", Self::COMMAND, uri)
} else {
Self::COMMAND.to_string()
}
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
let uri = parts
.next()
.map(|s| {
s.parse()
.map_err(|_| RequestParserError::SyntaxError(1, s.to_owned()))
})
.transpose()?;
debug_assert!(parts.next().is_none());
Ok(uri)
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
DbSelectionPrintResponse::parse(parts)
}
}
#[cfg(test)]
@@ -74,11 +68,11 @@ mod tests {
OK
"};
let result = ListAll::parse_raw_response(response);
let result = ListAll::parse_raw_response(response.as_bytes());
assert_eq!(
result,
Ok(vec![
Ok(ListAllResponse(vec![
DbSelectionPrintResponse::Directory(DbDirectoryInfo {
directory: PathBuf::from("albums"),
last_modified: None
@@ -165,7 +159,7 @@ mod tests {
playlist: PathBuf::from("albums/b/album b.m3u8"),
last_modified: None
})
])
]))
)
}
}

View File

@@ -1,47 +1,42 @@
use serde::{Deserialize, Serialize};
use crate::{
DbSelectionPrintResponse,
commands::{Command, RequestParserError, ResponseParserError},
commands::{
Command, CommandResponse, RequestParserError, ResponseParserError,
single_optional_item_command_request,
},
common::types::Uri,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct ListAllInfo;
pub type ListAllInfoResponse = Vec<DbSelectionPrintResponse>;
single_optional_item_command_request!(ListAllInfo, "listallinfo", Uri);
impl Command for ListAllInfo {
type Request = Option<Uri>;
// TODO: This is supposed to be a tree-like structure, with directories containing files and playlists
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ListAllInfoResponse(Vec<DbSelectionPrintResponse>);
impl CommandResponse<'_> for ListAllInfoResponse {
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
let result = DbSelectionPrintResponse::parse(parts)?;
Ok(ListAllInfoResponse(result))
}
}
impl Command<'_, '_> for ListAllInfo {
type Request = ListAllInfoRequest;
type Response = ListAllInfoResponse;
const COMMAND: &'static str = "listallinfo";
fn serialize_request(&self, request: Self::Request) -> String {
if let Some(uri) = request {
format!("{} {}", Self::COMMAND, uri)
} else {
Self::COMMAND.to_string()
}
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
let uri = parts
.next()
.map(|s| {
s.parse()
.map_err(|_| RequestParserError::SyntaxError(1, s.to_owned()))
})
.transpose()?;
debug_assert!(parts.next().is_none());
Ok(uri)
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
DbSelectionPrintResponse::parse(parts)
}
}
#[cfg(test)]
@@ -83,11 +78,11 @@ mod tests {
OK
"};
let result = ListAllInfo::parse_raw_response(response);
let result = ListAllInfo::parse_raw_response(response.as_bytes());
assert_eq!(
result,
Ok(vec![
Ok(ListAllInfoResponse(vec![
DbSelectionPrintResponse::Directory(DbDirectoryInfo {
directory: PathBuf::from("albums"),
last_modified: Some("2024-10-01T10:00:00Z".to_string()),
@@ -121,7 +116,7 @@ mod tests {
playlist: PathBuf::from("albums/a/album a.m3u8"),
last_modified: Some("2022-12-31T09:00:00Z".to_string())
})
]),
])),
)
}
}

View File

@@ -1,48 +1,32 @@
use serde::{Deserialize, Serialize};
use crate::{
DbDirectoryInfo, DbSelectionPrintResponse,
commands::{Command, RequestParserError, ResponseParserError},
commands::{
Command, CommandResponse, RequestParserError, ResponseParserError,
single_optional_item_command_request,
},
common::types::Uri,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct ListFiles;
// TODO: fix this type
pub type ListFilesResponse = Vec<DbDirectoryInfo>;
single_optional_item_command_request!(ListFiles, "listfiles", Uri);
impl Command for ListFiles {
type Request = Option<Uri>;
type Response = ListFilesResponse;
const COMMAND: &'static str = "listfiles";
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ListFilesResponse(Vec<DbDirectoryInfo>);
fn serialize_request(&self, request: Self::Request) -> String {
if let Some(uri) = request {
format!("{} {}", Self::COMMAND, uri)
} else {
Self::COMMAND.to_string()
}
impl CommandResponse<'_> for ListFilesResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
let uri = parts
.next()
.map(|s| {
s.parse()
.map_err(|_| RequestParserError::SyntaxError(1, s.to_owned()))
})
.transpose()?;
debug_assert!(parts.next().is_none());
Ok(uri)
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
// TODO: debug assert only toplevel dirs
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
DbSelectionPrintResponse::parse(parts)?
.into_iter()
.map(|i| match i {
@@ -54,50 +38,12 @@ impl Command for ListFiles {
Err(ResponseParserError::UnexpectedProperty("playlist"))
}
})
.collect::<Result<Self::Response, ResponseParserError<'_>>>()
.collect::<Result<Vec<_>, ResponseParserError<'_>>>()
.map(ListFilesResponse)
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use super::*;
use indoc::indoc;
use pretty_assertions::assert_eq;
#[test]
fn test_parse_response() {
let response = indoc! {"
directory: albums
Last-Modified: 2024-10-01T10:00:00Z
directory: playlists
Last-Modified: 2023-10-01T10:00:00Z
directory: Some Other Music
Last-Modified: 2022-10-01T10:00:00Z
OK
"};
let result = ListFiles::parse_raw_response(response);
assert_eq!(
result,
Ok(vec![
DbDirectoryInfo {
directory: PathBuf::from("albums"),
last_modified: Some("2024-10-01T10:00:00Z".to_string()),
},
DbDirectoryInfo {
directory: PathBuf::from("playlists"),
last_modified: Some("2023-10-01T10:00:00Z".to_string()),
},
DbDirectoryInfo {
directory: PathBuf::from("Some Other Music"),
last_modified: Some("2022-10-01T10:00:00Z".to_string()),
}
])
)
}
impl Command<'_, '_> for ListFiles {
type Request = ListFilesRequest;
type Response = ListFilesResponse;
}

View File

@@ -1,50 +1,41 @@
use serde::{Deserialize, Serialize};
use crate::{
DbSelectionPrintResponse,
commands::{Command, RequestParserError, ResponseParserError},
commands::{
Command, CommandResponse, RequestParserError, ResponseParserError,
single_optional_item_command_request,
},
common::types::Uri,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct LsInfo;
pub type LsInfoResponse = Vec<DbSelectionPrintResponse>;
single_optional_item_command_request!(LsInfo, "lsinfo", Uri);
impl Command for LsInfo {
type Request = Option<Uri>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LsInfoResponse(Vec<DbSelectionPrintResponse>);
impl CommandResponse<'_> for LsInfoResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
let result = DbSelectionPrintResponse::parse(parts)?;
Ok(LsInfoResponse(result))
}
}
impl Command<'_, '_> for LsInfo {
type Request = LsInfoRequest;
type Response = LsInfoResponse;
const COMMAND: &'static str = "lsinfo";
fn serialize_request(&self, request: Self::Request) -> String {
match request {
Some(uri) => format!("{} {}", Self::COMMAND, uri),
None => Self::COMMAND.to_string(),
}
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
let uri = parts
.next()
.map(|s| {
s.parse()
.map_err(|_| RequestParserError::SyntaxError(1, s.to_owned()))
})
.transpose()?;
debug_assert!(parts.next().is_none());
Ok(uri)
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
// TODO: debug_assert no song variants
// debug_assert!(
// result.iter().
DbSelectionPrintResponse::parse(parts)
}
}
#[cfg(test)]
@@ -75,11 +66,11 @@ mod tests {
OK
"};
let result = LsInfo::parse_raw_response(response);
let result = LsInfo::parse_raw_response(response.as_bytes());
assert_eq!(
result,
Ok(vec![
Ok(LsInfoResponse(vec![
DbSelectionPrintResponse::Directory(DbDirectoryInfo {
directory: PathBuf::from("albums"),
last_modified: Some("2024-10-01T10:00:00Z".to_string())
@@ -100,7 +91,7 @@ mod tests {
playlist: PathBuf::from("albums/b/album b.m3u8"),
last_modified: Some("2021-12-31T09:00:00Z".to_string())
})
])
])),
);
}
}

View File

@@ -1,39 +1,30 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, CommandResponse, ResponseParserError, single_item_command_request},
common::types::Uri,
request_tokenizer::RequestTokenizer,
response_tokenizer::{GenericResponseValue, ResponseAttributes},
};
pub struct ReadComments;
pub type ReadCommentsResponse = HashMap<String, String>;
single_item_command_request!(ReadComments, "readcomments", Uri);
impl Command for ReadComments {
type Request = Uri;
type Response = ReadCommentsResponse;
const COMMAND: &'static str = "readcomments";
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ReadCommentsResponse(HashMap<String, String>);
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {}", Self::COMMAND, request)
impl CommandResponse<'_> for ReadCommentsResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
let uri = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let uri = uri
.parse()
.map_err(|_| RequestParserError::SyntaxError(1, uri.to_owned()))?;
debug_assert!(parts.next().is_none());
Ok(uri)
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
let parts: HashMap<_, _> = parts.into_map()?;
let comments = parts
@@ -44,6 +35,11 @@ impl Command for ReadComments {
})
.collect::<Result<HashMap<_, _>, ResponseParserError>>()?;
Ok(comments)
Ok(ReadCommentsResponse(comments))
}
}
impl Command<'_, '_> for ReadComments {
type Request = ReadCommentsRequest;
type Response = ReadCommentsResponse;
}

View File

@@ -3,7 +3,7 @@ use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError},
common::types::{Offset, Uri},
request_tokenizer::RequestTokenizer,
response_tokenizer::{
@@ -19,23 +19,25 @@ pub struct ReadPictureRequest {
pub offset: Offset,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ReadPictureResponse {
pub size: usize,
pub binary: Vec<u8>,
pub mimetype: Option<String>,
}
impl Command for ReadPicture {
type Request = ReadPictureRequest;
type Response = Option<ReadPictureResponse>;
impl CommandRequest<'_> for ReadPictureRequest {
const COMMAND: &'static str = "readpicture";
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {} {}", Self::COMMAND, request.uri, request.offset)
fn into_request_enum(self) -> crate::Request {
crate::Request::ReadPicture(self.uri, self.offset)
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::ReadPicture(uri, offset) => Some(ReadPictureRequest { uri, offset }),
_ => None,
}
}
fn serialize(&self) -> String {
format!("{} {} {}", Self::COMMAND, self.uri, self.offset)
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let uri = match parts.next() {
Some(s) => s,
None => return Err(RequestParserError::UnexpectedEOF),
@@ -55,15 +57,31 @@ impl Command for ReadPicture {
offset,
})
}
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ReadPictureResponse {
pub size: usize,
pub binary: Vec<u8>,
pub mimetype: Option<String>,
}
impl CommandResponse<'_> for ReadPictureResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
let parts: HashMap<_, _> = parts.into_map()?;
if parts.is_empty() {
return Ok(None);
}
// TODO: is empty response possible?
// if parts.is_empty() {
// return Err(ResponseParserError::UnexpectedEOF);
// }
let size = get_and_parse_property!(parts, "size", Text);
@@ -71,10 +89,15 @@ impl Command for ReadPicture {
let mimetype = get_optional_property!(parts, "mimetype", Text).map(|s| s.to_string());
Ok(Some(ReadPictureResponse {
Ok(ReadPictureResponse {
size,
binary,
mimetype,
}))
})
}
}
impl Command<'_, '_> for ReadPicture {
type Request = ReadPictureRequest;
type Response = ReadPictureResponse;
}

View File

@@ -3,42 +3,33 @@ use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{
Command, CommandResponse, RequestParserError, ResponseParserError,
single_optional_item_command_request,
},
common::types::Uri,
request_tokenizer::RequestTokenizer,
response_tokenizer::{ResponseAttributes, get_and_parse_property},
};
pub struct Rescan;
single_optional_item_command_request!(Rescan, "rescan", Uri);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RescanResponse {
pub updating_db: usize,
}
impl Command for Rescan {
type Request = Option<Uri>;
type Response = RescanResponse;
const COMMAND: &'static str = "rescan";
fn serialize_request(&self, request: Self::Request) -> String {
match request {
Some(uri) => format!("{} {}", Self::COMMAND, uri),
None => Self::COMMAND.to_string(),
}
impl CommandResponse<'_> for RescanResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
let uri = parts.next().map(|s| s.to_string());
debug_assert!(parts.next().is_none());
Ok(uri)
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, 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);
@@ -46,3 +37,8 @@ impl Command for Rescan {
Ok(RescanResponse { updating_db })
}
}
impl Command<'_, '_> for Rescan {
type Request = RescanRequest;
type Response = RescanResponse;
}

View File

@@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
use crate::{
DbSelectionPrintResponse, DbSongInfo,
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError},
common::types::{Sort, WindowRange},
filter::Filter,
request_tokenizer::RequestTokenizer,
@@ -18,26 +18,36 @@ pub struct SearchRequest {
window: Option<WindowRange>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SearchResponse(Vec<DbSongInfo>);
impl Command for Search {
type Request = SearchRequest;
type Response = SearchResponse;
impl CommandRequest<'_> for SearchRequest {
const COMMAND: &'static str = "search";
fn serialize_request(&self, request: Self::Request) -> String {
let mut cmd = format!("{} {}", Self::COMMAND, request.filter);
if let Some(sort) = request.sort {
fn into_request_enum(self) -> crate::Request {
crate::Request::Search(self.filter, self.sort, self.window)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::Search(filter, sort, window) => Some(SearchRequest {
filter,
sort,
window,
}),
_ => None,
}
}
fn serialize(&self) -> String {
let mut cmd = format!("{} {}", Self::COMMAND, self.filter);
if let Some(sort) = &self.sort {
cmd.push_str(&format!(" sort {}", sort));
}
if let Some(window) = request.window {
if let Some(window) = &self.window {
cmd.push_str(&format!(" window {}", window));
}
cmd
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let filter = match parts.next() {
Some(f) => {
Filter::parse(f).map_err(|_| RequestParserError::SyntaxError(1, f.to_owned()))?
@@ -73,10 +83,21 @@ impl Command for Search {
window,
})
}
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SearchResponse(Vec<DbSongInfo>);
impl CommandResponse<'_> for SearchResponse {
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
DbSelectionPrintResponse::parse(parts)?
.into_iter()
.map(|i| match i {
@@ -92,3 +113,8 @@ impl Command for Search {
.map(SearchResponse)
}
}
impl Command<'_, '_> for Search {
type Request = SearchRequest;
type Response = SearchResponse;
}

View File

@@ -1,7 +1,7 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, RequestParserError, ResponseAttributes, ResponseParserError},
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
common::types::{SongPosition, Sort, WindowRange},
filter::Filter,
request_tokenizer::RequestTokenizer,
@@ -17,26 +17,40 @@ pub struct SearchAddRequest {
position: Option<SongPosition>,
}
impl Command for SearchAdd {
type Request = SearchAddRequest;
type Response = ();
impl CommandRequest<'_> for SearchAddRequest {
const COMMAND: &'static str = "searchadd";
fn serialize_request(&self, request: Self::Request) -> String {
let mut cmd = format!("{} {}", Self::COMMAND, request.filter);
if let Some(sort) = request.sort {
fn into_request_enum(self) -> crate::Request {
crate::Request::SearchAdd(self.filter, self.sort, self.window, self.position)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::SearchAdd(filter, sort, window, position) => Some(SearchAddRequest {
filter,
sort,
window,
position,
}),
_ => None,
}
}
fn serialize(&self) -> String {
let mut cmd = format!("{} {}", Self::COMMAND, self.filter);
if let Some(sort) = &self.sort {
cmd.push_str(&format!(" sort {}", sort));
}
if let Some(window) = request.window {
if let Some(window) = &self.window {
cmd.push_str(&format!(" window {}", window));
}
if let Some(position) = request.position {
if let Some(position) = &self.position {
cmd.push_str(&format!(" position {}", position));
}
cmd
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let filter = match parts.next() {
Some(f) => {
Filter::parse(f).map_err(|_| RequestParserError::SyntaxError(1, f.to_owned()))?
@@ -83,11 +97,11 @@ impl Command for SearchAdd {
position,
})
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
}
empty_command_response!(SearchAdd);
impl Command<'_, '_> for SearchAdd {
type Request = SearchAddRequest;
type Response = SearchAddResponse;
}

View File

@@ -1,7 +1,7 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, RequestParserError, ResponseAttributes, ResponseParserError},
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
common::types::{PlaylistName, SongPosition, Sort, WindowRange},
filter::Filter,
request_tokenizer::RequestTokenizer,
@@ -18,31 +18,49 @@ pub struct SearchAddPlRequest {
position: Option<SongPosition>,
}
impl Command for SearchAddPl {
type Request = SearchAddPlRequest;
type Response = ();
impl CommandRequest<'_> for SearchAddPlRequest {
const COMMAND: &'static str = "searchaddpl";
fn serialize_request(&self, request: Self::Request) -> String {
let mut cmd = format!(
"{} {} {}",
Self::COMMAND,
request.playlist_name,
request.filter
);
if let Some(sort) = request.sort {
fn into_request_enum(self) -> crate::Request {
crate::Request::SearchAddPl(
self.playlist_name,
self.filter,
self.sort,
self.window,
self.position,
)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::SearchAddPl(playlist_name, filter, sort, window, position) => {
Some(SearchAddPlRequest {
playlist_name,
filter,
sort,
window,
position,
})
}
_ => None,
}
}
fn serialize(&self) -> String {
let mut cmd = format!("{} {} {}", Self::COMMAND, self.playlist_name, self.filter);
if let Some(sort) = &self.sort {
cmd.push_str(&format!(" sort {}", sort));
}
if let Some(window) = request.window {
if let Some(window) = &self.window {
cmd.push_str(&format!(" window {}", window));
}
if let Some(position) = request.position {
if let Some(position) = &self.position {
cmd.push_str(&format!(" position {}", position));
}
cmd
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let playlist_name = parts
.next()
.ok_or(RequestParserError::UnexpectedEOF)?
@@ -95,11 +113,11 @@ impl Command for SearchAddPl {
position,
})
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
}
empty_command_response!(SearchAddPl);
impl Command<'_, '_> for SearchAddPl {
type Request = SearchAddPlRequest;
type Response = SearchAddPlResponse;
}

View File

@@ -3,7 +3,8 @@ use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
Seconds,
commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError},
common::types::GroupType,
filter::Filter,
request_tokenizer::RequestTokenizer,
@@ -18,26 +19,31 @@ pub struct SearchCountRequest {
group: Option<GroupType>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SearchCountResponse {
pub songs: usize,
pub playtime: u64,
}
impl Command for SearchCount {
type Request = SearchCountRequest;
type Response = SearchCountResponse;
impl CommandRequest<'_> for SearchCountRequest {
const COMMAND: &'static str = "searchcount";
fn serialize_request(&self, request: Self::Request) -> String {
let mut cmd = format!("{} {}", Self::COMMAND, request.filter);
if let Some(group) = request.group {
fn into_request_enum(self) -> crate::Request {
crate::Request::SearchCount(self.filter, self.group)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::SearchCount(filter, group) => {
Some(SearchCountRequest { filter, group })
}
_ => None,
}
}
fn serialize(&self) -> String {
let mut cmd = format!("{} {}", Self::COMMAND, self.filter);
if let Some(group) = &self.group {
cmd.push_str(&format!(" group {}", group));
}
cmd
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let filter = match parts.next() {
Some(f) => {
Filter::parse(f).map_err(|_| RequestParserError::SyntaxError(1, f.to_owned()))?
@@ -60,10 +66,24 @@ impl Command for SearchCount {
Ok(SearchCountRequest { filter, group })
}
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SearchCountResponse {
pub songs: usize,
pub playtime: Seconds,
}
impl CommandResponse<'_> for SearchCountResponse {
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
let parts: HashMap<_, _> = parts.into_map()?;
let songs = get_and_parse_property!(parts, "songs", Text);
@@ -72,3 +92,8 @@ impl Command for SearchCount {
Ok(SearchCountResponse { songs, playtime })
}
}
impl Command<'_, '_> for SearchCount {
type Request = SearchCountRequest;
type Response = SearchCountResponse;
}

View File

@@ -3,42 +3,33 @@ use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{
Command, CommandResponse, RequestParserError, ResponseParserError,
single_optional_item_command_request,
},
common::types::Uri,
request_tokenizer::RequestTokenizer,
response_tokenizer::{ResponseAttributes, get_and_parse_property},
};
pub struct Update;
single_optional_item_command_request!(Update, "update", Uri);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct UpdateResponse {
updating_db: usize,
}
impl Command for Update {
type Request = Option<Uri>;
type Response = UpdateResponse;
const COMMAND: &'static str = "update";
fn serialize_request(&self, request: Self::Request) -> String {
match request {
Some(uri) => format!("{} {}", Self::COMMAND, uri),
None => Self::COMMAND.to_string(),
}
impl CommandResponse<'_> for UpdateResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
let uri = parts.next().map(|s| s.to_string());
debug_assert!(parts.next().is_none());
Ok(uri)
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, 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);
@@ -46,3 +37,8 @@ impl Command for Update {
Ok(UpdateResponse { updating_db })
}
}
impl Command<'_, '_> for Update {
type Request = UpdateRequest;
type Response = UpdateResponse;
}

View File

@@ -1,11 +1,11 @@
pub mod delpartition;
pub mod listpartitions;
pub mod moveoutput;
pub mod newpartition;
pub mod partition;
mod delpartition;
mod listpartitions;
mod moveoutput;
mod newpartition;
mod partition;
pub use delpartition::DelPartition;
pub use listpartitions::ListPartitions;
pub use moveoutput::MoveOutput;
pub use newpartition::NewPartition;
pub use partition::Partition;
pub use delpartition::*;
pub use listpartitions::*;
pub use moveoutput::*;
pub use newpartition::*;
pub use partition::*;

View File

@@ -1,36 +1,15 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, empty_command_response, single_item_command_request},
common::types::PartitionName,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct DelPartition;
impl Command for DelPartition {
type Request = PartitionName;
type Response = ();
const COMMAND: &'static str = "delpartition";
single_item_command_request!(DelPartition, "delpartition", PartitionName);
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {}", Self::COMMAND, request)
}
empty_command_response!(DelPartition);
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
let partition = parts
.next()
.ok_or(RequestParserError::UnexpectedEOF)?
.to_string();
debug_assert!(parts.next().is_none());
Ok(partition)
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
impl Command<'_, '_> for DelPartition {
type Request = DelPartitionRequest;
type Response = DelPartitionResponse;
}

View File

@@ -1,42 +1,16 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, ResponseParserError, empty_command_request, multi_item_command_response},
common::types::PartitionName,
request_tokenizer::RequestTokenizer,
response_tokenizer::{ResponseAttributes, expect_property_type},
response_tokenizer::expect_property_type,
};
pub struct ListPartitions;
pub type ListPartitionsResponse = Vec<PartitionName>;
empty_command_request!(ListPartitions, "listpartitions");
impl Command for ListPartitions {
type Request = ();
multi_item_command_response!(ListPartitions, "partition", PartitionName);
impl Command<'_, '_> for ListPartitions {
type Request = ListPartitionsRequest;
type Response = ListPartitionsResponse;
const COMMAND: &'static str = "listpartitions";
fn serialize_request(&self, _request: Self::Request) -> String {
Self::COMMAND.to_string()
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
debug_assert!(parts.next().is_none());
Ok(())
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
let parts: Vec<_> = parts.into_vec()?;
let mut partitions = Vec::with_capacity(parts.len());
for (key, value) in parts.into_iter() {
if key != "partition" {
return Err(ResponseParserError::UnexpectedProperty(key));
}
let partition = expect_property_type!(Some(value), "partition", Text).to_string();
partitions.push(partition);
}
Ok(partitions)
}
}

View File

@@ -1,35 +1,12 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
use crate::commands::{Command, empty_command_response, single_item_command_request};
pub struct MoveOutput;
impl Command for MoveOutput {
type Request = String;
type Response = ();
const COMMAND: &'static str = "moveoutput";
single_item_command_request!(MoveOutput, "moveoutput", String);
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {}", Self::COMMAND, request)
}
empty_command_response!(MoveOutput);
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
let output_name = parts
.next()
.ok_or(RequestParserError::UnexpectedEOF)?
.to_string();
debug_assert!(parts.next().is_none());
Ok(output_name)
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
impl Command<'_, '_> for MoveOutput {
type Request = MoveOutputRequest;
type Response = MoveOutputResponse;
}

View File

@@ -1,36 +1,15 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, empty_command_response, single_item_command_request},
common::types::PartitionName,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct NewPartition;
impl Command for NewPartition {
type Request = PartitionName;
type Response = ();
const COMMAND: &'static str = "newpartition";
single_item_command_request!(NewPartition, "newpartition", PartitionName);
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {}", Self::COMMAND, request)
}
empty_command_response!(NewPartition);
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
let partition = parts
.next()
.ok_or(RequestParserError::UnexpectedEOF)?
.to_string();
debug_assert!(parts.next().is_none());
Ok(partition)
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
impl Command<'_, '_> for NewPartition {
type Request = NewPartitionRequest;
type Response = NewPartitionResponse;
}

View File

@@ -1,36 +1,15 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, empty_command_response, single_item_command_request},
common::types::PartitionName,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct Partition;
impl Command for Partition {
type Request = PartitionName;
type Response = ();
const COMMAND: &'static str = "partition";
single_item_command_request!(Partition, "partition", PartitionName);
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {}", Self::COMMAND, request)
}
empty_command_response!(Partition);
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
let partition = parts
.next()
.ok_or(RequestParserError::UnexpectedEOF)?
.to_string();
debug_assert!(parts.next().is_none());
Ok(partition)
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
impl Command<'_, '_> for Partition {
type Request = PartitionRequest;
type Response = PartitionResponse;
}

View File

@@ -1,25 +1,25 @@
pub mod consume;
pub mod crossfade;
pub mod getvol;
pub mod mixrampdb;
pub mod mixrampdelay;
pub mod random;
pub mod repeat;
pub mod replay_gain_mode;
pub mod replay_gain_status;
pub mod setvol;
pub mod single;
pub mod volume;
mod consume;
mod crossfade;
mod getvol;
mod mixrampdb;
mod mixrampdelay;
mod random;
mod repeat;
mod replay_gain_mode;
mod replay_gain_status;
mod setvol;
mod single;
mod volume;
pub use consume::Consume;
pub use crossfade::Crossfade;
pub use getvol::GetVol;
pub use mixrampdb::MixRampDb;
pub use mixrampdelay::MixRampDelay;
pub use random::Random;
pub use repeat::Repeat;
pub use replay_gain_mode::ReplayGainMode;
pub use replay_gain_status::ReplayGainStatus;
pub use setvol::SetVol;
pub use single::Single;
pub use volume::Volume;
pub use consume::*;
pub use crossfade::*;
pub use getvol::*;
pub use mixrampdb::*;
pub use mixrampdelay::*;
pub use random::*;
pub use repeat::*;
pub use replay_gain_mode::*;
pub use replay_gain_status::*;
pub use setvol::*;
pub use single::*;
pub use volume::*;

View File

@@ -1,39 +1,15 @@
use std::str::FromStr;
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, empty_command_response, single_item_command_request},
common::types::BoolOrOneshot,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct Consume;
impl Command for Consume {
type Request = BoolOrOneshot;
type Response = ();
const COMMAND: &'static str = "consume";
single_item_command_request!(Consume, "consume", BoolOrOneshot);
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {}", Self::COMMAND, request)
}
empty_command_response!(Consume);
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
let state = match parts.next() {
Some(s) => crate::common::types::BoolOrOneshot::from_str(s)
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
debug_assert!(parts.next().is_none());
Ok(state)
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
impl Command<'_, '_> for Consume {
type Request = ConsumeRequest;
type Response = ConsumeResponse;
}

View File

@@ -1,38 +1,15 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, empty_command_response, single_item_command_request},
common::types::Seconds,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct Crossfade;
impl Command for Crossfade {
type Request = Seconds;
type Response = ();
const COMMAND: &'static str = "crossfade";
single_item_command_request!(Crossfade, "crossfade", Seconds);
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {}", Self::COMMAND, request)
}
empty_command_response!(Crossfade);
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
let seconds = match parts.next() {
Some(s) => s
.parse::<Seconds>()
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
debug_assert!(parts.next().is_none());
Ok(seconds)
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
impl Command<'_, '_> for Crossfade {
type Request = CrossfadeRequest;
type Response = CrossfadeResponse;
}

View File

@@ -1,34 +1,15 @@
use std::collections::HashMap;
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, empty_command_request, single_item_command_response},
common::types::VolumeValue,
request_tokenizer::RequestTokenizer,
response_tokenizer::{ResponseAttributes, get_and_parse_property},
};
pub struct GetVol;
impl Command for GetVol {
type Request = ();
type Response = VolumeValue;
const COMMAND: &'static str = "getvol";
empty_command_request!(GetVol, "getvol");
fn serialize_request(&self, _request: Self::Request) -> String {
Self::COMMAND.to_string()
}
single_item_command_response!(GetVol, "volume", VolumeValue);
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
debug_assert!(parts.next().is_none());
Ok(())
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
let parts: HashMap<_, _> = parts.into_map()?;
assert_eq!(parts.len(), 1);
let volume = get_and_parse_property!(parts, "volume", Text);
Ok(volume)
}
impl Command<'_, '_> for GetVol {
type Request = GetVolRequest;
type Response = GetVolResponse;
}

View File

@@ -1,37 +1,12 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
use crate::commands::{Command, empty_command_response, single_item_command_request};
pub struct MixRampDb;
impl Command for MixRampDb {
type Request = f32;
type Response = ();
const COMMAND: &'static str = "mixrampdb";
single_item_command_request!(MixRampDb, "mixrampdb", f32);
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {}", Self::COMMAND, request)
}
empty_command_response!(MixRampDb);
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
let db = match parts.next() {
Some(s) => s
.parse::<f32>()
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
debug_assert!(parts.next().is_none());
Ok(db)
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
impl Command<'_, '_> for MixRampDb {
type Request = MixRampDbRequest;
type Response = MixRampDbResponse;
}

View File

@@ -1,38 +1,14 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, empty_command_response, single_item_command_request},
common::types::Seconds,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct MixRampDelay;
impl Command for MixRampDelay {
type Request = Seconds;
type Response = ();
const COMMAND: &'static str = "mixrampdelay";
single_item_command_request!(MixRampDelay, "mixrampdelay", Seconds);
empty_command_response!(MixRampDelay);
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {}", Self::COMMAND, request)
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
let seconds = match parts.next() {
Some(s) => s
.parse::<Seconds>()
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
debug_assert!(parts.next().is_none());
Ok(seconds)
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
impl Command<'_, '_> for MixRampDelay {
type Request = MixRampDelayRequest;
type Response = MixRampDelayResponse;
}

View File

@@ -1,22 +1,32 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct Random;
impl Command for Random {
type Request = bool;
type Response = ();
pub struct RandomRequest(bool);
impl CommandRequest<'_> for RandomRequest {
const COMMAND: &'static str = "random";
fn serialize_request(&self, request: Self::Request) -> String {
let state = if request { "1" } else { "0" };
fn into_request_enum(self) -> crate::Request {
crate::Request::Random(self.0)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::Random(state) => Some(RandomRequest(state)),
_ => None,
}
}
fn serialize(&self) -> String {
let state = if self.0 { "1" } else { "0" };
format!("{} {}", Self::COMMAND, state)
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let state = match parts.next() {
Some("0") => false,
Some("1") => true,
@@ -26,13 +36,13 @@ impl Command for Random {
debug_assert!(parts.next().is_none());
Ok(state)
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
Ok(RandomRequest(state))
}
}
empty_command_response!(Random);
impl Command<'_, '_> for Random {
type Request = RandomRequest;
type Response = RandomResponse;
}

View File

@@ -1,22 +1,32 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct Repeat;
impl Command for Repeat {
type Request = bool;
type Response = ();
pub struct RepeatRequest(bool);
impl CommandRequest<'_> for RepeatRequest {
const COMMAND: &'static str = "repeat";
fn serialize_request(&self, request: Self::Request) -> String {
let state = if request { "1" } else { "0" };
fn into_request_enum(self) -> crate::Request {
crate::Request::Repeat(self.0)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::Repeat(state) => Some(RepeatRequest(state)),
_ => None,
}
}
fn serialize(&self) -> String {
let state = if self.0 { "1" } else { "0" };
format!("{} {}", Self::COMMAND, state)
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let state = match parts.next() {
Some("0") => false,
Some("1") => true,
@@ -26,13 +36,13 @@ impl Command for Repeat {
debug_assert!(parts.next().is_none());
Ok(state)
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
Ok(RepeatRequest(state))
}
}
empty_command_response!(Repeat);
impl Command<'_, '_> for Repeat {
type Request = RepeatRequest;
type Response = RepeatResponse;
}

View File

@@ -1,39 +1,15 @@
use std::str::FromStr;
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, empty_command_response, single_item_command_request},
common::types::ReplayGainModeMode,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct ReplayGainMode;
impl Command for ReplayGainMode {
type Request = ReplayGainModeMode;
type Response = ();
const COMMAND: &'static str = "replay_gain_mode";
single_item_command_request!(ReplayGainMode, "replay_gain_mode", ReplayGainModeMode);
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {}", Self::COMMAND, request)
}
empty_command_response!(ReplayGainMode);
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
let mode = match parts.next() {
Some(s) => ReplayGainModeMode::from_str(s)
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
debug_assert!(parts.next().is_none());
Ok(mode)
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
impl Command<'_, '_> for ReplayGainMode {
type Request = ReplayGainModeRequest;
type Response = ReplayGainModeResponse;
}

View File

@@ -3,36 +3,30 @@ use std::{collections::HashMap, str::FromStr};
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, CommandResponse, ResponseParserError, empty_command_request},
common::types::ReplayGainModeMode,
request_tokenizer::RequestTokenizer,
response_tokenizer::{ResponseAttributes, get_property},
};
pub struct ReplayGainStatus;
empty_command_request!(ReplayGainStatus, "replay_gain_status");
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ReplayGainStatusResponse {
pub replay_gain_mode: ReplayGainModeMode,
}
impl Command for ReplayGainStatus {
type Request = ();
type Response = ReplayGainStatusResponse;
const COMMAND: &'static str = "replay_gain_status";
fn serialize_request(&self, _: Self::Request) -> String {
Self::COMMAND.to_string()
impl CommandResponse<'_> for ReplayGainStatusResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
debug_assert!(parts.next().is_none());
Ok(())
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, 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);
@@ -43,3 +37,8 @@ impl Command for ReplayGainStatus {
})
}
}
impl Command<'_, '_> for ReplayGainStatus {
type Request = ReplayGainStatusRequest;
type Response = ReplayGainStatusResponse;
}

View File

@@ -1,39 +1,15 @@
use std::str::FromStr;
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, empty_command_response, single_item_command_request},
common::types::VolumeValue,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct SetVol;
impl Command for SetVol {
type Request = VolumeValue;
type Response = ();
const COMMAND: &'static str = "setvol";
single_item_command_request!(SetVol, "setvol", VolumeValue);
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {}", Self::COMMAND, request)
}
empty_command_response!(SetVol);
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
let volume = match parts.next() {
Some(s) => VolumeValue::from_str(s)
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
debug_assert!(parts.next().is_none());
Ok(volume)
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
impl Command<'_, '_> for SetVol {
type Request = SetVolRequest;
type Response = SetVolResponse;
}

View File

@@ -1,39 +1,15 @@
use std::str::FromStr;
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, empty_command_response, single_item_command_request},
common::types::BoolOrOneshot,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct Single;
impl Command for Single {
type Request = BoolOrOneshot;
type Response = ();
const COMMAND: &'static str = "single";
single_item_command_request!(Single, "single", BoolOrOneshot);
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {}", Self::COMMAND, request)
}
empty_command_response!(Single);
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
let state = match parts.next() {
Some(s) => crate::common::types::BoolOrOneshot::from_str(s)
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
debug_assert!(parts.next().is_none());
Ok(state)
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
impl Command<'_, '_> for Single {
type Request = SingleRequest;
type Response = SingleResponse;
}

View File

@@ -1,39 +1,15 @@
use std::str::FromStr;
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, empty_command_response, single_item_command_request},
common::types::VolumeValue,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct Volume;
impl Command for Volume {
type Request = VolumeValue;
type Response = ();
const COMMAND: &'static str = "volume";
single_item_command_request!(Volume, "volume", VolumeValue);
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {}", Self::COMMAND, request)
}
empty_command_response!(Volume);
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
let change = match parts.next() {
Some(s) => VolumeValue::from_str(s)
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
debug_assert!(parts.next().is_none());
Ok(change)
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
impl Command<'_, '_> for Volume {
type Request = VolumeRequest;
type Response = VolumeResponse;
}

View File

@@ -1,11 +1,11 @@
pub mod clearerror;
pub mod currentsong;
pub mod idle;
pub mod stats;
pub mod status;
mod clearerror;
mod currentsong;
mod idle;
mod stats;
mod status;
pub use clearerror::ClearError;
pub use currentsong::CurrentSong;
pub use idle::Idle;
pub use stats::Stats;
pub use status::Status;
pub use clearerror::*;
pub use currentsong::*;
pub use idle::*;
pub use stats::*;
pub use status::*;

View File

@@ -1,31 +1,13 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
use crate::commands::{Command, empty_command_request, empty_command_response};
/// Clears the current error message in status (this is also accomplished by any command that starts playback)
pub struct ClearError;
impl Command for ClearError {
type Request = ();
type Response = ();
const COMMAND: &'static str = "clearerror";
empty_command_request!(ClearError, "clearerror");
fn serialize_request(&self, _request: Self::Request) -> String {
Self::COMMAND.to_string()
}
empty_command_response!(ClearError);
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
debug_assert!(parts.next().is_none());
Ok(())
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
impl Command<'_, '_> for ClearError {
type Request = ClearErrorRequest;
type Response = ClearErrorResponse;
}

View File

@@ -1,35 +1,33 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
request_tokenizer::RequestTokenizer,
commands::{Command, CommandResponse, ResponseParserError, empty_command_request},
response_tokenizer::ResponseAttributes,
};
/// Displays the song info of the current song (same song that is identified in status)
pub struct CurrentSong;
empty_command_request!(CurrentSong, "currentsong");
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CurrentSongResponse {}
impl Command for CurrentSong {
type Request = ();
type Response = CurrentSongResponse;
const COMMAND: &'static str = "currentsong";
fn serialize_request(&self, _request: Self::Request) -> String {
Self::COMMAND.to_string()
impl CommandResponse<'_> for CurrentSongResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
debug_assert!(parts.next().is_none());
Ok(())
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
fn parse_response(
_parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
unimplemented!()
}
}
impl Command<'_, '_> for CurrentSong {
type Request = CurrentSongRequest;
type Response = CurrentSongResponse;
}

View File

@@ -1,23 +1,31 @@
use std::str::FromStr;
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
common::types::SubSystem,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct Idle;
pub type IdleRequest = Option<Vec<SubSystem>>;
pub struct IdleRequest(Option<Vec<SubSystem>>);
impl Command for Idle {
type Request = IdleRequest;
type Response = ();
impl CommandRequest<'_> for IdleRequest {
const COMMAND: &'static str = "idle";
fn serialize_request(&self, request: Self::Request) -> String {
fn into_request_enum(self) -> crate::Request {
crate::Request::Idle(self.0)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::Idle(subsystems) => Some(IdleRequest(subsystems)),
_ => None,
}
}
fn serialize(&self) -> String {
match &self.0 {
Some(subsystems) => {
let subsystems_str = subsystems
.iter()
@@ -30,7 +38,8 @@ impl Command for Idle {
}
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
fn parse(parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let mut parts = parts;
let result = parts.next().map_or(Ok(None), |subsystems| {
let subsystems = subsystems
.split(',')
@@ -41,13 +50,13 @@ impl Command for Idle {
debug_assert!(parts.next().is_none());
result
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
result.map(IdleRequest)
}
}
empty_command_response!(Idle);
impl Command<'_, '_> for Idle {
type Request = IdleRequest;
type Response = IdleResponse;
}

View File

@@ -3,8 +3,7 @@ use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
request_tokenizer::RequestTokenizer,
commands::{Command, CommandResponse, ResponseParserError, empty_command_request},
response_tokenizer::{
ResponseAttributes, get_and_parse_optional_property, get_and_parse_property,
},
@@ -12,6 +11,8 @@ use crate::{
pub struct Stats;
empty_command_request!(Stats, "stats");
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct StatsResponse {
pub uptime: u64,
@@ -23,24 +24,16 @@ pub struct StatsResponse {
pub db_update: Option<u64>,
}
impl Command for Stats {
type Request = ();
type Response = StatsResponse;
const COMMAND: &'static str = "stats";
fn serialize_request(&self, _: Self::Request) -> String {
Self::COMMAND.to_string()
impl CommandResponse<'_> for StatsResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
debug_assert!(parts.next().is_none());
Ok(())
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
let parts: HashMap<_, _> = parts.into_map()?;
let uptime = get_and_parse_property!(parts, "uptime", Text);
@@ -62,3 +55,8 @@ impl Command for Stats {
})
}
}
impl Command<'_, '_> for Stats {
type Request = StatsRequest;
type Response = StatsResponse;
}

View File

@@ -4,15 +4,18 @@ use std::str::FromStr;
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, CommandResponse, ResponseParserError, empty_command_request},
common::types::{Audio, BoolOrOneshot, SongId, SongPosition},
request_tokenizer::RequestTokenizer,
response_tokenizer::{
ResponseAttributes, get_and_parse_optional_property, get_and_parse_property,
get_optional_property, get_property,
},
};
pub struct Status;
empty_command_request!(Status, "status");
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum StatusResponseState {
Play,
@@ -62,124 +65,114 @@ pub struct StatusResponse {
pub last_loaded_playlist: Option<String>,
}
#[inline]
fn parse_status_response(
parts: ResponseAttributes<'_>,
) -> Result<StatusResponse, ResponseParserError<'_>> {
let parts: HashMap<_, _> = parts.into_map()?;
let partition = get_property!(parts, "partition", Text).to_string();
impl CommandResponse<'_> for StatusResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
}
let volume = match get_property!(parts, "volume", Text) {
"-1" => None,
volume => Some(
volume
.parse()
.map_err(|_| ResponseParserError::InvalidProperty("volume", volume))?,
),
};
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
let repeat = match get_property!(parts, "repeat", Text) {
"0" => Ok(false),
"1" => Ok(true),
repeat => Err(ResponseParserError::InvalidProperty("repeat", repeat)),
}?;
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
let parts: HashMap<_, _> = parts.into_map()?;
let partition = get_property!(parts, "partition", Text).to_string();
let random = match get_property!(parts, "random", Text) {
"0" => Ok(false),
"1" => Ok(true),
random => Err(ResponseParserError::InvalidProperty("random", random)),
}?;
let volume = match get_property!(parts, "volume", Text) {
"-1" => None,
volume => Some(
volume
.parse()
.map_err(|_| ResponseParserError::InvalidProperty("volume", volume))?,
),
};
let single = get_and_parse_property!(parts, "single", Text);
let consume = get_and_parse_property!(parts, "consume", Text);
let playlist: u32 = get_and_parse_property!(parts, "playlist", Text);
let playlist_length: u64 = get_and_parse_property!(parts, "playlistlength", Text);
let state: StatusResponseState = get_and_parse_property!(parts, "state", Text);
let song: Option<SongPosition> = get_and_parse_optional_property!(parts, "song", Text);
let song_id: Option<SongId> = get_and_parse_optional_property!(parts, "songid", Text);
let next_song: Option<SongPosition> = get_and_parse_optional_property!(parts, "nextsong", Text);
let next_song_id: Option<SongId> = get_and_parse_optional_property!(parts, "nextsongid", Text);
let repeat = match get_property!(parts, "repeat", Text) {
"0" => Ok(false),
"1" => Ok(true),
repeat => Err(ResponseParserError::InvalidProperty("repeat", repeat)),
}?;
let time = match get_optional_property!(parts, "time", Text) {
Some(time) => {
let mut parts = time.split(':');
let elapsed = parts
.next()
.ok_or(ResponseParserError::SyntaxError(0, time))?
.parse()
.map_err(|_| ResponseParserError::InvalidProperty("time", time))?;
let duration = parts
.next()
.ok_or(ResponseParserError::SyntaxError(0, time))?
.parse()
.map_err(|_| ResponseParserError::InvalidProperty("time", time))?;
Some((elapsed, duration))
}
None => None,
};
let random = match get_property!(parts, "random", Text) {
"0" => Ok(false),
"1" => Ok(true),
random => Err(ResponseParserError::InvalidProperty("random", random)),
}?;
let elapsed = get_and_parse_optional_property!(parts, "elapsed", Text);
let duration = get_and_parse_optional_property!(parts, "duration", Text);
let bitrate = get_and_parse_optional_property!(parts, "bitrate", Text);
let xfade = get_and_parse_optional_property!(parts, "xfade", Text);
let mixrampdb = get_and_parse_optional_property!(parts, "mixrampdb", Text);
let mixrampdelay = get_and_parse_optional_property!(parts, "mixrampdelay", Text);
let audio = get_and_parse_optional_property!(parts, "audio", Text);
let updating_db = get_and_parse_optional_property!(parts, "updating_db", Text);
let error = get_and_parse_optional_property!(parts, "error", Text);
let last_loaded_playlist =
get_and_parse_optional_property!(parts, "last_loaded_playlist", Text);
let single = get_and_parse_property!(parts, "single", Text);
let consume = get_and_parse_property!(parts, "consume", Text);
let playlist: u32 = get_and_parse_property!(parts, "playlist", Text);
let playlist_length: u64 = get_and_parse_property!(parts, "playlistlength", Text);
let state: StatusResponseState = get_and_parse_property!(parts, "state", Text);
let song: Option<SongPosition> = get_and_parse_optional_property!(parts, "song", Text);
let song_id: Option<SongId> = get_and_parse_optional_property!(parts, "songid", Text);
let next_song: Option<SongPosition> =
get_and_parse_optional_property!(parts, "nextsong", Text);
let next_song_id: Option<SongId> =
get_and_parse_optional_property!(parts, "nextsongid", Text);
Ok(StatusResponse {
partition,
volume,
repeat,
random,
single,
consume,
playlist,
playlist_length,
state,
song,
song_id,
next_song,
next_song_id,
time,
elapsed,
duration,
bitrate,
xfade,
mixrampdb,
mixrampdelay,
audio,
updating_db,
error,
last_loaded_playlist,
})
let time = match get_optional_property!(parts, "time", Text) {
Some(time) => {
let mut parts = time.split(':');
let elapsed = parts
.next()
.ok_or(ResponseParserError::SyntaxError(0, time))?
.parse()
.map_err(|_| ResponseParserError::InvalidProperty("time", time))?;
let duration = parts
.next()
.ok_or(ResponseParserError::SyntaxError(0, time))?
.parse()
.map_err(|_| ResponseParserError::InvalidProperty("time", time))?;
Some((elapsed, duration))
}
None => None,
};
let elapsed = get_and_parse_optional_property!(parts, "elapsed", Text);
let duration = get_and_parse_optional_property!(parts, "duration", Text);
let bitrate = get_and_parse_optional_property!(parts, "bitrate", Text);
let xfade = get_and_parse_optional_property!(parts, "xfade", Text);
let mixrampdb = get_and_parse_optional_property!(parts, "mixrampdb", Text);
let mixrampdelay = get_and_parse_optional_property!(parts, "mixrampdelay", Text);
let audio = get_and_parse_optional_property!(parts, "audio", Text);
let updating_db = get_and_parse_optional_property!(parts, "updating_db", Text);
let error = get_and_parse_optional_property!(parts, "error", Text);
let last_loaded_playlist =
get_and_parse_optional_property!(parts, "last_loaded_playlist", Text);
Ok(StatusResponse {
partition,
volume,
repeat,
random,
single,
consume,
playlist,
playlist_length,
state,
song,
song_id,
next_song,
next_song_id,
time,
elapsed,
duration,
bitrate,
xfade,
mixrampdb,
mixrampdelay,
audio,
updating_db,
error,
last_loaded_playlist,
})
}
}
pub struct Status;
impl Command for Status {
type Request = ();
impl Command<'_, '_> for Status {
type Request = StatusRequest;
type Response = StatusResponse;
const COMMAND: &'static str = "status";
fn serialize_request(&self, _request: Self::Request) -> String {
Self::COMMAND.to_string()
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
debug_assert!(parts.next().is_none());
Ok(())
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
parse_status_response(parts)
}
}
#[cfg(test)]
@@ -214,7 +207,7 @@ mod tests {
"# };
assert_eq!(
Status::parse_raw_response(contents),
Status::parse_raw_response(contents.as_bytes()),
Ok(StatusResponse {
partition: "default".into(),
volume: Some(66),

View File

@@ -1,45 +1,45 @@
pub mod add;
pub mod addid;
pub mod addtagid;
pub mod clear;
pub mod cleartagid;
pub mod delete;
pub mod deleteid;
pub mod move_;
pub mod moveid;
pub mod playlist;
pub mod playlistfind;
pub mod playlistid;
pub mod playlistinfo;
pub mod playlistsearch;
pub mod plchanges;
pub mod plchangesposid;
pub mod prio;
pub mod prioid;
pub mod rangeid;
pub mod shuffle;
pub mod swap;
pub mod swapid;
mod add;
mod addid;
mod addtagid;
mod clear;
mod cleartagid;
mod delete;
mod deleteid;
mod move_;
mod moveid;
mod playlist;
mod playlistfind;
mod playlistid;
mod playlistinfo;
mod playlistsearch;
mod plchanges;
mod plchangesposid;
mod prio;
mod prioid;
mod rangeid;
mod shuffle;
mod swap;
mod swapid;
pub use add::Add;
pub use addid::AddId;
pub use addtagid::AddTagId;
pub use clear::Clear;
pub use cleartagid::ClearTagId;
pub use delete::Delete;
pub use deleteid::DeleteId;
pub use move_::Move;
pub use moveid::MoveId;
pub use playlist::Playlist;
pub use playlistfind::PlaylistFind;
pub use playlistid::PlaylistId;
pub use playlistinfo::PlaylistInfo;
pub use playlistsearch::PlaylistSearch;
pub use plchanges::PlChanges;
pub use plchangesposid::PlChangesPosId;
pub use prio::Prio;
pub use prioid::PrioId;
pub use rangeid::RangeId;
pub use shuffle::Shuffle;
pub use swap::Swap;
pub use swapid::SwapId;
pub use add::*;
pub use addid::*;
pub use addtagid::*;
pub use clear::*;
pub use cleartagid::*;
pub use delete::*;
pub use deleteid::*;
pub use move_::*;
pub use moveid::*;
pub use playlist::*;
pub use playlistfind::*;
pub use playlistid::*;
pub use playlistinfo::*;
pub use playlistsearch::*;
pub use plchanges::*;
pub use plchangesposid::*;
pub use prio::*;
pub use prioid::*;
pub use rangeid::*;
pub use shuffle::*;
pub use swap::*;
pub use swapid::*;

View File

@@ -1,10 +1,9 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
common::types::{SongPosition, Uri},
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct Add;
@@ -15,25 +14,28 @@ pub struct AddRequest {
position: Option<SongPosition>,
}
impl AddRequest {
pub fn new(uri: Uri, position: Option<SongPosition>) -> Self {
Self { uri, position }
}
}
impl Command for Add {
type Request = AddRequest;
type Response = ();
impl CommandRequest<'_> for AddRequest {
const COMMAND: &'static str = "add";
fn serialize_request(&self, request: Self::Request) -> String {
match request.position {
Some(position) => format!("{} {} {}", Self::COMMAND, request.uri, position),
None => format!("{} {}", Self::COMMAND, request.uri),
fn into_request_enum(self) -> crate::Request {
crate::Request::Add(self.uri, self.position)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::Add(uri, position) => Some(AddRequest { uri, position }),
_ => None,
}
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
fn serialize(&self) -> String {
match self.position {
Some(position) => format!("{} {} {}", Self::COMMAND, self.uri, position),
None => format!("{} {}", Self::COMMAND, self.uri),
}
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let uri = match parts.next() {
Some(s) => s,
None => return Err(RequestParserError::UnexpectedEOF),
@@ -54,11 +56,11 @@ impl Command for Add {
position,
})
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
}
empty_command_response!(Add);
impl Command<'_, '_> for Add {
type Request = AddRequest;
type Response = AddResponse;
}

View File

@@ -1,7 +1,7 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError},
common::types::{SongId, SongPosition, Uri},
request_tokenizer::RequestTokenizer,
response_tokenizer::{ResponseAttributes, get_next_and_parse_property},
@@ -15,24 +15,28 @@ pub struct AddIdRequest {
pub position: Option<SongPosition>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AddIdResponse {
pub id: SongId,
}
impl Command for AddId {
type Request = AddIdRequest;
type Response = AddIdResponse;
impl CommandRequest<'_> for AddIdRequest {
const COMMAND: &'static str = "addid";
fn serialize_request(&self, request: Self::Request) -> String {
match request.position {
Some(pos) => format!("{} {} {}", Self::COMMAND, request.uri, pos),
None => format!("{} {}", Self::COMMAND, request.uri),
fn into_request_enum(self) -> crate::Request {
crate::Request::AddId(self.uri, self.position)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::AddId(uri, position) => Some(AddIdRequest { uri, position }),
_ => None,
}
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
fn serialize(&self) -> String {
match self.position {
Some(pos) => format!("{} {} {}", Self::COMMAND, self.uri, pos),
None => format!("{} {}", Self::COMMAND, self.uri),
}
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let uri = match parts.next() {
Some(s) => s,
None => return Err(RequestParserError::UnexpectedEOF),
@@ -53,10 +57,23 @@ impl Command for AddId {
position,
})
}
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AddIdResponse {
pub id: SongId,
}
impl CommandResponse<'_> for AddIdResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
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);
@@ -66,3 +83,8 @@ impl Command for AddId {
Ok(AddIdResponse { id })
}
}
impl Command<'_, '_> for AddId {
type Request = AddIdRequest;
type Response = AddIdResponse;
}

View File

@@ -1,7 +1,7 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, RequestParserError, ResponseAttributes, ResponseParserError},
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
common::types::{SongId, TagName, TagValue},
request_tokenizer::RequestTokenizer,
};
@@ -15,22 +15,35 @@ pub struct AddTagIdRequest {
pub tag_value: TagValue,
}
impl Command for AddTagId {
type Request = AddTagIdRequest;
type Response = ();
impl CommandRequest<'_> for AddTagIdRequest {
const COMMAND: &'static str = "addtagid";
fn serialize_request(&self, request: Self::Request) -> String {
fn into_request_enum(self) -> crate::Request {
crate::Request::AddTagId(self.songid, self.tag_name, self.tag_value)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::AddTagId(songid, tag_name, tag_value) => Some(AddTagIdRequest {
songid,
tag_name,
tag_value,
}),
_ => None,
}
}
fn serialize(&self) -> String {
format!(
"{} {} {} {}",
Self::COMMAND,
request.songid,
request.tag_name,
request.tag_value
self.songid,
self.tag_name,
self.tag_value
)
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let songid = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let songid = songid
.parse()
@@ -54,11 +67,11 @@ impl Command for AddTagId {
tag_value,
})
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
}
empty_command_response!(AddTagId);
impl Command<'_, '_> for AddTagId {
type Request = AddTagIdRequest;
type Response = AddTagIdResponse;
}

View File

@@ -1,29 +1,12 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
use crate::commands::{Command, empty_command_request, empty_command_response};
pub struct Clear;
impl Command for Clear {
type Request = ();
type Response = ();
const COMMAND: &'static str = "clear";
empty_command_request!(Clear, "clear");
fn serialize_request(&self, _request: Self::Request) -> String {
Self::COMMAND.to_string()
}
empty_command_response!(Clear);
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
debug_assert!(parts.next().is_none());
Ok(())
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
impl Command<'_, '_> for Clear {
type Request = ClearRequest;
type Response = ClearResponse;
}

View File

@@ -1,10 +1,9 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
common::types::{SongId, TagName},
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct ClearTagId;
@@ -15,16 +14,27 @@ pub struct ClearTagIdRequest {
pub tag_name: TagName,
}
impl Command for ClearTagId {
type Request = ClearTagIdRequest;
type Response = ();
impl CommandRequest<'_> for ClearTagIdRequest {
const COMMAND: &'static str = "cleartagid";
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {} {}", Self::COMMAND, request.songid, request.tag_name)
fn into_request_enum(self) -> crate::Request {
crate::Request::ClearTagId(self.songid, self.tag_name)
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::ClearTagId(songid, tag_name) => {
Some(ClearTagIdRequest { songid, tag_name })
}
_ => None,
}
}
fn serialize(&self) -> String {
format!("{} {} {}", Self::COMMAND, self.songid, self.tag_name)
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let songid = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let songid = songid
.parse()
@@ -39,11 +49,11 @@ impl Command for ClearTagId {
Ok(ClearTagIdRequest { songid, tag_name })
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
}
empty_command_response!(ClearTagId);
impl Command<'_, '_> for ClearTagId {
type Request = ClearTagIdRequest;
type Response = ClearTagIdResponse;
}

View File

@@ -1,37 +1,15 @@
use std::str::FromStr;
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, empty_command_response, single_item_command_request},
common::types::OneOrRange,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct Delete;
impl Command for Delete {
type Request = OneOrRange;
type Response = ();
const COMMAND: &'static str = "delete";
single_item_command_request!(Delete, "delete", OneOrRange);
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {}", Self::COMMAND, request)
}
empty_command_response!(Delete);
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
let pos_or_range = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let one_or_range = OneOrRange::from_str(pos_or_range)
.map_err(|_| RequestParserError::SyntaxError(0, pos_or_range.to_string()))?;
debug_assert!(parts.next().is_none());
Ok(one_or_range)
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
impl Command<'_, '_> for Delete {
type Request = DeleteRequest;
type Response = DeleteResponse;
}

View File

@@ -1,36 +1,15 @@
use crate::{
commands::{Command, RequestParserError, ResponseParserError},
commands::{Command, empty_command_response, single_item_command_request},
common::types::SongId,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
};
pub struct DeleteId;
impl Command for DeleteId {
type Request = SongId;
type Response = ();
const COMMAND: &'static str = "deleteid";
single_item_command_request!(DeleteId, "deleteid", SongId);
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {}", Self::COMMAND, request)
}
empty_command_response!(DeleteId);
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
let id = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let id = id
.parse()
.map_err(|_| RequestParserError::SyntaxError(0, id.to_string()))?;
debug_assert!(parts.next().is_none());
Ok(id)
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
impl Command<'_, '_> for DeleteId {
type Request = DeleteIdRequest;
type Response = DeleteIdResponse;
}

View File

@@ -1,7 +1,7 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, RequestParserError, ResponseAttributes, ResponseParserError},
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
common::types::{AbsouluteRelativeSongPosition, OneOrRange},
request_tokenizer::RequestTokenizer,
};
@@ -14,16 +14,25 @@ pub struct MoveRequest {
pub to: AbsouluteRelativeSongPosition,
}
impl Command for Move {
type Request = MoveRequest;
type Response = ();
impl CommandRequest<'_> for MoveRequest {
const COMMAND: &'static str = "move";
fn serialize_request(&self, request: Self::Request) -> String {
format!("{} {} {}", Self::COMMAND, request.from_or_range, request.to)
fn into_request_enum(self) -> crate::Request {
crate::Request::Move(self.from_or_range, self.to)
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> Result<Self::Request, RequestParserError> {
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::Move(from_or_range, to) => Some(MoveRequest { from_or_range, to }),
_ => None,
}
}
fn serialize(&self) -> String {
format!("{} {} {}", Self::COMMAND, self.from_or_range, self.to)
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let from_or_range = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let from_or_range = from_or_range
.parse()
@@ -38,11 +47,11 @@ impl Command for Move {
Ok(MoveRequest { from_or_range, to })
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
}
empty_command_response!(Move);
impl Command<'_, '_> for Move {
type Request = MoveRequest;
type Response = MoveResponse;
}

Some files were not shown because too many files have changed in this diff Show More