diff --git a/Cargo.toml b/Cargo.toml index 847766f..6089f71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ rust-version = "1.85.0" [dependencies] chrono = { version = "0.4.42", features = ["serde"] } lalrpop-util = { version = "0.22.2", features = ["lexer"] } +paste = "1.0.15" serde = { version = "1.0.228", features = ["derive"] } [dev-dependencies] diff --git a/src/commands.rs b/src/commands.rs index 77ac391..45f3885 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -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; + + /// 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; + + /// 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 { + 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; + + // /// Serializes the response into a Vec. + // fn serialize(&self) -> Vec; + + /// Parses the response from its tokenized parts. + /// See also [`parse_raw`]. + fn parse(parts: ResponseAttributes<'a>) -> Result>; + + /// Parses the response from its raw byte representation. + fn parse_raw(raw: &'a [u8]) -> Result> { + 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 { self.serialize_request(request).into_bytes() } - // fn serialize_response(&self) -> String; - // fn serialize_response_to_bytes(&self) -> Vec { - // 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; - - /// 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::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::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::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::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 { + 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 { + 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 { + todo!() + } + + fn parse( + _parts: crate::commands::ResponseAttributes<'_>, + ) -> Result> { + 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 { + 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 { + 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 { + 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 { + 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 { + todo!() + } + + fn parse( + parts: crate::commands::ResponseAttributes<'_>, + ) -> Result> { + 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 { + todo!() + } + + fn parse( + parts: crate::commands::ResponseAttributes<'_>, + ) -> Result> { + // 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 { diff --git a/src/commands/audio_output_devices.rs b/src/commands/audio_output_devices.rs index 411bb72..12681d4 100644 --- a/src/commands/audio_output_devices.rs +++ b/src/commands/audio_output_devices.rs @@ -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::*; diff --git a/src/commands/audio_output_devices/disableoutput.rs b/src/commands/audio_output_devices/disableoutput.rs index 54371b1..d6e8136 100644 --- a/src/commands/audio_output_devices/disableoutput.rs +++ b/src/commands/audio_output_devices/disableoutput.rs @@ -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 { - 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> { - debug_assert!(parts.is_empty()); - Ok(()) - } + type Response = DisableOutputResponse; } diff --git a/src/commands/audio_output_devices/enableoutput.rs b/src/commands/audio_output_devices/enableoutput.rs index 424e43d..3b96010 100644 --- a/src/commands/audio_output_devices/enableoutput.rs +++ b/src/commands/audio_output_devices/enableoutput.rs @@ -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 { - 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> { - debug_assert!(parts.is_empty()); - Ok(()) - } + type Response = EnableOutputResponse; } diff --git a/src/commands/audio_output_devices/outputs.rs b/src/commands/audio_output_devices/outputs.rs index ca5f858..d0bea5c 100644 --- a/src/commands/audio_output_devices/outputs.rs +++ b/src/commands/audio_output_devices/outputs.rs @@ -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); + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Output { pub id: AudioOutputId, @@ -20,25 +24,16 @@ pub struct Output { pub attribute: HashMap, } -pub type OutputsResponse = Vec; - -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 { - debug_assert!(parts.next().is_none()); - Ok(()) + fn from_response_enum(response: crate::Response) -> Option { + todo!() } - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { + fn parse(parts: ResponseAttributes<'_>) -> Result> { let mut outputs = Vec::new(); let mut id: Option = 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 }, }, - ]) + ])), ); } } diff --git a/src/commands/audio_output_devices/outputset.rs b/src/commands/audio_output_devices/outputset.rs index 1b4f5d6..2e6a44a 100644 --- a/src/commands/audio_output_devices/outputset.rs +++ b/src/commands/audio_output_devices/outputset.rs @@ -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 { + 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 { + fn parse(mut parts: RequestTokenizer<'_>) -> Result { 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> { - debug_assert!(parts.is_empty()); - Ok(()) - } +} + +empty_command_response!(OutputSet); + +impl Command<'_, '_> for OutputSet { + type Request = OutputSetRequest; + type Response = OutputSetResponse; } diff --git a/src/commands/audio_output_devices/toggleoutput.rs b/src/commands/audio_output_devices/toggleoutput.rs index 35d8bb0..4b426de 100644 --- a/src/commands/audio_output_devices/toggleoutput.rs +++ b/src/commands/audio_output_devices/toggleoutput.rs @@ -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 { - 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> { - debug_assert!(parts.is_empty()); - Ok(()) - } + type Response = ToggleOutputResponse; } diff --git a/src/commands/client_to_client.rs b/src/commands/client_to_client.rs index d8adc90..602db88 100644 --- a/src/commands/client_to_client.rs +++ b/src/commands/client_to_client.rs @@ -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::*; diff --git a/src/commands/client_to_client/channels.rs b/src/commands/client_to_client/channels.rs index 2c460e0..2601c8a 100644 --- a/src/commands/client_to_client/channels.rs +++ b/src/commands/client_to_client/channels.rs @@ -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, } -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 { - debug_assert!(parts.next().is_none()); - - Ok(()) + fn from_response_enum(response: crate::Response) -> Option { + todo!() } - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { + fn parse(parts: ResponseAttributes<'_>) -> Result> { 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 { diff --git a/src/commands/client_to_client/readmessages.rs b/src/commands/client_to_client/readmessages.rs index aa8652a..cca5e91 100644 --- a/src/commands/client_to_client/readmessages.rs +++ b/src/commands/client_to_client/readmessages.rs @@ -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; +empty_command_request!(ReadMessages, "readmessages"); + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ReadMessagesResponse(Vec); #[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 { - debug_assert!(parts.next().is_none()); - - Ok(()) + fn from_response_enum(response: crate::Response) -> Option { + todo!() } - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { + fn parse(parts: ResponseAttributes<'_>) -> Result> { 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(), }, - ]) + ])) ); } } diff --git a/src/commands/client_to_client/sendmessage.rs b/src/commands/client_to_client/sendmessage.rs index 371123e..f2a3b0f 100644 --- a/src/commands/client_to_client/sendmessage.rs +++ b/src/commands/client_to_client/sendmessage.rs @@ -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 { + fn from_request_enum(request: crate::Request) -> Option { + 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 { 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> { - debug_assert!(parts.is_empty()); - Ok(()) - } +} + +empty_command_response!(SendMessage); + +impl Command<'_, '_> for SendMessage { + type Request = SendMessageRequest; + type Response = SendMessageResponse; } diff --git a/src/commands/client_to_client/subscribe.rs b/src/commands/client_to_client/subscribe.rs index 97c9281..2b01819 100644 --- a/src/commands/client_to_client/subscribe.rs +++ b/src/commands/client_to_client/subscribe.rs @@ -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 { - 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> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for Subscribe { + type Request = SubscribeRequest; + type Response = SubscribeResponse; } diff --git a/src/commands/client_to_client/unsubscribe.rs b/src/commands/client_to_client/unsubscribe.rs index 05fe2ee..8271c46 100644 --- a/src/commands/client_to_client/unsubscribe.rs +++ b/src/commands/client_to_client/unsubscribe.rs @@ -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 { - 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> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for Unsubscribe { + type Request = UnsubscribeRequest; + type Response = UnsubscribeResponse; } diff --git a/src/commands/connection_settings.rs b/src/commands/connection_settings.rs index 4c1579c..ec12583 100644 --- a/src/commands/connection_settings.rs +++ b/src/commands/connection_settings.rs @@ -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::*; diff --git a/src/commands/connection_settings/binary_limit.rs b/src/commands/connection_settings/binary_limit.rs index 2e9b121..dca234e 100644 --- a/src/commands/connection_settings/binary_limit.rs +++ b/src/commands/connection_settings/binary_limit.rs @@ -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 { - 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> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for BinaryLimit { + type Request = BinaryLimitRequest; + type Response = BinaryLimitResponse; } diff --git a/src/commands/connection_settings/close.rs b/src/commands/connection_settings/close.rs index 6b8a061..b34c16a 100644 --- a/src/commands/connection_settings/close.rs +++ b/src/commands/connection_settings/close.rs @@ -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 { - debug_assert!(parts.next().is_none()); - Ok(()) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for Close { + type Request = CloseRequest; + type Response = CloseResponse; } diff --git a/src/commands/connection_settings/kill.rs b/src/commands/connection_settings/kill.rs index 5a48014..c646587 100644 --- a/src/commands/connection_settings/kill.rs +++ b/src/commands/connection_settings/kill.rs @@ -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 { - debug_assert!(parts.next().is_none()); - Ok(()) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for Kill { + type Request = KillRequest; + type Response = KillResponse; } diff --git a/src/commands/connection_settings/password.rs b/src/commands/connection_settings/password.rs index 9350fed..3f8c92c 100644 --- a/src/commands/connection_settings/password.rs +++ b/src/commands/connection_settings/password.rs @@ -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 { - let password = parts - .next() - .ok_or(RequestParserError::UnexpectedEOF)? - .to_string(); - debug_assert!(parts.next().is_none()); - Ok(password) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for Password { + type Request = PasswordRequest; + type Response = PasswordResponse; } diff --git a/src/commands/connection_settings/ping.rs b/src/commands/connection_settings/ping.rs index dadb930..4463b0b 100644 --- a/src/commands/connection_settings/ping.rs +++ b/src/commands/connection_settings/ping.rs @@ -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 { - debug_assert!(parts.next().is_none()); - Ok(()) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for Ping { + type Request = PingRequest; + type Response = PingResponse; } diff --git a/src/commands/connection_settings/protocol.rs b/src/commands/connection_settings/protocol.rs index 2ed3326..3c1e8e4 100644 --- a/src/commands/connection_settings/protocol.rs +++ b/src/commands/connection_settings/protocol.rs @@ -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; +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 { - debug_assert!(parts.next().is_none()); - Ok(()) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - 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::, ResponseParserError>>()?; - - Ok(list) - } } diff --git a/src/commands/connection_settings/protocol_all.rs b/src/commands/connection_settings/protocol_all.rs index 4fb49ba..69b865b 100644 --- a/src/commands/connection_settings/protocol_all.rs +++ b/src/commands/connection_settings/protocol_all.rs @@ -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 { - debug_assert!(parts.next().is_none()); - Ok(()) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for ProtocolAll { + type Request = ProtocolAllRequest; + type Response = ProtocolAllResponse; } diff --git a/src/commands/connection_settings/protocol_available.rs b/src/commands/connection_settings/protocol_available.rs index 528ee43..cf9d34d 100644 --- a/src/commands/connection_settings/protocol_available.rs +++ b/src/commands/connection_settings/protocol_available.rs @@ -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; +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 { - debug_assert!(parts.next().is_none()); - Ok(()) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - 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::, ResponseParserError>>()?; - - Ok(list) - } } diff --git a/src/commands/connection_settings/protocol_clear.rs b/src/commands/connection_settings/protocol_clear.rs index 21c3939..a722c4a 100644 --- a/src/commands/connection_settings/protocol_clear.rs +++ b/src/commands/connection_settings/protocol_clear.rs @@ -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 { - debug_assert!(parts.next().is_none()); - Ok(()) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for ProtocolClear { + type Request = ProtocolClearRequest; + type Response = ProtocolClearResponse; } diff --git a/src/commands/connection_settings/protocol_disable.rs b/src/commands/connection_settings/protocol_disable.rs index 727e7ba..77d0f99 100644 --- a/src/commands/connection_settings/protocol_disable.rs +++ b/src/commands/connection_settings/protocol_disable.rs @@ -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; +pub struct ProtocolDisableRequest(Vec); -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 { + fn from_request_enum(request: crate::Request) -> Option { + 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::>() + .join(" "); + format!("{} {}", Self::COMMAND, features) + } + + fn parse(parts: RequestTokenizer<'_>) -> Result { let mut parts = parts.peekable(); if parts.peek().is_none() { return Err(RequestParserError::UnexpectedEOF); } - let features = parts.map(|s| s.to_string()).collect::>(); + 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> { - debug_assert!(parts.is_empty()); - Ok(()) + Ok(ProtocolDisableRequest(features)) } } + +empty_command_response!(ProtocolDisable); + +impl Command<'_, '_> for ProtocolDisable { + type Request = ProtocolDisableRequest; + type Response = ProtocolDisableResponse; +} diff --git a/src/commands/connection_settings/protocol_enable.rs b/src/commands/connection_settings/protocol_enable.rs index bac14b9..716234b 100644 --- a/src/commands/connection_settings/protocol_enable.rs +++ b/src/commands/connection_settings/protocol_enable.rs @@ -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; +pub struct ProtocolEnableRequest(Vec); -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 { + fn from_request_enum(request: crate::Request) -> Option { + 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::>() + .join(" "); + format!("{} {}", Self::COMMAND, features) + } + + fn parse(parts: RequestTokenizer<'_>) -> Result { let mut parts = parts.peekable(); if parts.peek().is_none() { return Err(RequestParserError::UnexpectedEOF); } - let features = parts.map(|s| s.to_string()).collect::>(); + 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> { - debug_assert!(parts.is_empty()); - Ok(()) + Ok(ProtocolEnableRequest(features)) } } + +empty_command_response!(ProtocolEnable); + +impl Command<'_, '_> for ProtocolEnable { + type Request = ProtocolEnableRequest; + type Response = ProtocolEnableResponse; +} diff --git a/src/commands/connection_settings/tag_types.rs b/src/commands/connection_settings/tag_types.rs index 356cb54..9e99907 100644 --- a/src/commands/connection_settings/tag_types.rs +++ b/src/commands/connection_settings/tag_types.rs @@ -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; +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 { - debug_assert!(parts.next().is_none()); - Ok(()) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - 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) - } } diff --git a/src/commands/connection_settings/tag_types_all.rs b/src/commands/connection_settings/tag_types_all.rs index de41601..5348881 100644 --- a/src/commands/connection_settings/tag_types_all.rs +++ b/src/commands/connection_settings/tag_types_all.rs @@ -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 { - debug_assert!(parts.next().is_none()); - Ok(()) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for TagTypesAll { + type Request = TagTypesAllRequest; + type Response = TagTypesAllResponse; } diff --git a/src/commands/connection_settings/tag_types_available.rs b/src/commands/connection_settings/tag_types_available.rs index 609cf4e..e58ac10 100644 --- a/src/commands/connection_settings/tag_types_available.rs +++ b/src/commands/connection_settings/tag_types_available.rs @@ -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; +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 { - debug_assert!(parts.next().is_none()); - Ok(()) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - 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) - } } diff --git a/src/commands/connection_settings/tag_types_clear.rs b/src/commands/connection_settings/tag_types_clear.rs index 2f68c7b..c2e3e26 100644 --- a/src/commands/connection_settings/tag_types_clear.rs +++ b/src/commands/connection_settings/tag_types_clear.rs @@ -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 { - debug_assert!(parts.next().is_none()); - Ok(()) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for TagTypesClear { + type Request = TagTypesClearRequest; + type Response = TagTypesClearResponse; } diff --git a/src/commands/connection_settings/tag_types_disable.rs b/src/commands/connection_settings/tag_types_disable.rs index 5be6ba0..b48da8d 100644 --- a/src/commands/connection_settings/tag_types_disable.rs +++ b/src/commands/connection_settings/tag_types_disable.rs @@ -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; +pub struct TagTypesDisableRequest(Vec); -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 { + fn from_request_enum(request: crate::Request) -> Option { + 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::>() + .join(" ") + ) + } + + fn parse(parts: RequestTokenizer<'_>) -> Result { let mut parts = parts.peekable(); if parts.peek().is_none() { return Err(RequestParserError::UnexpectedEOF); } - let tag_types = parts.map(|s| s.to_string()).collect::>(); + let tag_types = parts + .map(|s| { + s.parse() + .map_err(|_| RequestParserError::SyntaxError(1, s.to_owned())) + }) + .collect::, RequestParserError>>()?; - Ok(tag_types) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) + Ok(TagTypesDisableRequest(tag_types)) } } + +empty_command_response!(TagTypesDisable); + +impl Command<'_, '_> for TagTypesDisable { + type Request = TagTypesDisableRequest; + type Response = TagTypesDisableResponse; +} diff --git a/src/commands/connection_settings/tag_types_enable.rs b/src/commands/connection_settings/tag_types_enable.rs index c408767..b4e2ded 100644 --- a/src/commands/connection_settings/tag_types_enable.rs +++ b/src/commands/connection_settings/tag_types_enable.rs @@ -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; +pub struct TagTypesEnableRequest(Vec); -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 { + fn from_request_enum(request: crate::Request) -> Option { + 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::>() + .join(" ") + ) + } + + fn parse(parts: RequestTokenizer<'_>) -> Result { let mut parts = parts.peekable(); if parts.peek().is_none() { return Err(RequestParserError::UnexpectedEOF); } - let tag_types = parts.map(|s| s.to_string()).collect::>(); + let tag_types = parts + .map(|s| { + s.parse() + .map_err(|_| RequestParserError::SyntaxError(1, s.to_owned())) + }) + .collect::, RequestParserError>>()?; - Ok(tag_types) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) + Ok(TagTypesEnableRequest(tag_types)) } } + +empty_command_response!(TagTypesEnable); + +impl Command<'_, '_> for TagTypesEnable { + type Request = TagTypesEnableRequest; + type Response = TagTypesEnableResponse; +} diff --git a/src/commands/connection_settings/tag_types_reset.rs b/src/commands/connection_settings/tag_types_reset.rs index ca8b29b..c03a176 100644 --- a/src/commands/connection_settings/tag_types_reset.rs +++ b/src/commands/connection_settings/tag_types_reset.rs @@ -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; +pub struct TagTypesResetRequest(Vec); -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 { + fn from_request_enum(request: crate::Request) -> Option { + 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::>() + .join(" ") + ) + } + + fn parse(parts: RequestTokenizer<'_>) -> Result { 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::>(); + let tag_types = parts + .map(|s| { + s.parse() + .map_err(|_| RequestParserError::SyntaxError(1, s.to_owned())) + }) + .collect::, RequestParserError>>()?; - Ok(tag_types) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) + Ok(TagTypesResetRequest(tag_types)) } } + +empty_command_response!(TagTypesReset); + +impl Command<'_, '_> for TagTypesReset { + type Request = TagTypesResetRequest; + type Response = TagTypesResetResponse; +} diff --git a/src/commands/controlling_playback.rs b/src/commands/controlling_playback.rs index 6b6b7c3..1575ead 100644 --- a/src/commands/controlling_playback.rs +++ b/src/commands/controlling_playback.rs @@ -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::*; diff --git a/src/commands/controlling_playback/next.rs b/src/commands/controlling_playback/next.rs index 16745ba..de29dfc 100644 --- a/src/commands/controlling_playback/next.rs +++ b/src/commands/controlling_playback/next.rs @@ -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 { - debug_assert!(parts.next().is_none()); - Ok(()) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for Next { + type Request = NextRequest; + type Response = NextResponse; } diff --git a/src/commands/controlling_playback/pause.rs b/src/commands/controlling_playback/pause.rs index fbafcc3..d1807a3 100644 --- a/src/commands/controlling_playback/pause.rs +++ b/src/commands/controlling_playback/pause.rs @@ -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; - type Response = (); +pub struct PauseRequest(Option); + +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 { 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 { + fn parse(mut parts: RequestTokenizer<'_>) -> Result { 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> { - debug_assert!(parts.is_empty()); - Ok(()) + result.map(PauseRequest) } } + +empty_command_response!(Pause); + +impl Command<'_, '_> for Pause { + type Request = PauseRequest; + type Response = PauseResponse; +} diff --git a/src/commands/controlling_playback/play.rs b/src/commands/controlling_playback/play.rs index 4504c44..83c0e8a 100644 --- a/src/commands/controlling_playback/play.rs +++ b/src/commands/controlling_playback/play.rs @@ -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 { - let songpos = match parts.next() { - Some(s) => s - .parse::() - .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> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for Play { + type Request = PlayRequest; + type Response = PlayResponse; } diff --git a/src/commands/controlling_playback/playid.rs b/src/commands/controlling_playback/playid.rs index 6ab6f9d..1002059 100644 --- a/src/commands/controlling_playback/playid.rs +++ b/src/commands/controlling_playback/playid.rs @@ -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 { - let songid = match parts.next() { - Some(s) => s - .parse::() - .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> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for PlayId { + type Request = PlayIdRequest; + type Response = PlayIdResponse; } diff --git a/src/commands/controlling_playback/previous.rs b/src/commands/controlling_playback/previous.rs index 5a8f589..d4ea8e1 100644 --- a/src/commands/controlling_playback/previous.rs +++ b/src/commands/controlling_playback/previous.rs @@ -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 { - debug_assert!(parts.next().is_none()); - Ok(()) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for Previous { + type Request = PreviousRequest; + type Response = PreviousResponse; } diff --git a/src/commands/controlling_playback/seek.rs b/src/commands/controlling_playback/seek.rs index 2b15b33..f531329 100644 --- a/src/commands/controlling_playback/seek.rs +++ b/src/commands/controlling_playback/seek.rs @@ -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 { + fn from_request_enum(request: crate::Request) -> Option { + 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 { let songpos = match parts.next() { Some(s) => s .parse::() @@ -43,11 +51,11 @@ impl Command for Seek { Ok(SeekRequest { songpos, time }) } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) - } +} + +empty_command_response!(Seek); + +impl Command<'_, '_> for Seek { + type Request = SeekRequest; + type Response = SeekResponse; } diff --git a/src/commands/controlling_playback/seekcur.rs b/src/commands/controlling_playback/seekcur.rs index 0f20925..470f75a 100644 --- a/src/commands/controlling_playback/seekcur.rs +++ b/src/commands/controlling_playback/seekcur.rs @@ -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 { + 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 { + fn parse(mut parts: RequestTokenizer<'_>) -> Result { 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> { - debug_assert!(parts.is_empty()); - Ok(()) - } +} + +empty_command_response!(SeekCur); + +impl Command<'_, '_> for SeekCur { + type Request = SeekCurRequest; + type Response = SeekCurResponse; } diff --git a/src/commands/controlling_playback/seekid.rs b/src/commands/controlling_playback/seekid.rs index 404243f..9cc7f77 100644 --- a/src/commands/controlling_playback/seekid.rs +++ b/src/commands/controlling_playback/seekid.rs @@ -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 { + fn from_request_enum(request: crate::Request) -> Option { + 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 { let songid = match parts.next() { Some(s) => s .parse::() @@ -43,11 +51,11 @@ impl Command for SeekId { Ok(SeekIdRequest { songid, time }) } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) - } +} + +empty_command_response!(SeekId); + +impl Command<'_, '_> for SeekId { + type Request = SeekIdRequest; + type Response = SeekIdResponse; } diff --git a/src/commands/controlling_playback/stop.rs b/src/commands/controlling_playback/stop.rs index a55075c..c25ee8b 100644 --- a/src/commands/controlling_playback/stop.rs +++ b/src/commands/controlling_playback/stop.rs @@ -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 { - debug_assert!(parts.next().is_none()); - Ok(()) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for Stop { + type Request = StopRequest; + type Response = StopResponse; } diff --git a/src/commands/mounts_and_neighbors.rs b/src/commands/mounts_and_neighbors.rs index 09f378d..2a49081 100644 --- a/src/commands/mounts_and_neighbors.rs +++ b/src/commands/mounts_and_neighbors.rs @@ -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::*; diff --git a/src/commands/mounts_and_neighbors/listmounts.rs b/src/commands/mounts_and_neighbors/listmounts.rs index 5c10ab0..b713854 100644 --- a/src/commands/mounts_and_neighbors/listmounts.rs +++ b/src/commands/mounts_and_neighbors/listmounts.rs @@ -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; - 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 { - debug_assert!(parts.next().is_none()); - Ok(()) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - 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(), + ])) + ); } } diff --git a/src/commands/mounts_and_neighbors/listneighbors.rs b/src/commands/mounts_and_neighbors/listneighbors.rs index 8285843..e79ebbd 100644 --- a/src/commands/mounts_and_neighbors/listneighbors.rs +++ b/src/commands/mounts_and_neighbors/listneighbors.rs @@ -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; +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); - 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 { - debug_assert!(parts.next().is_none()); - Ok(()) + fn from_response_enum(response: crate::Response) -> Option { + todo!() } - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { + fn parse(parts: ResponseAttributes<'_>) -> Result> { 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; +} diff --git a/src/commands/mounts_and_neighbors/mount.rs b/src/commands/mounts_and_neighbors/mount.rs index ff920e4..b001786 100644 --- a/src/commands/mounts_and_neighbors/mount.rs +++ b/src/commands/mounts_and_neighbors/mount.rs @@ -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 { + fn from_request_enum(request: crate::Request) -> Option { + 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(""), + self.uri + ) + } + + fn parse(mut parts: RequestTokenizer<'_>) -> Result { 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> { - debug_assert!(parts.is_empty()); - Ok(()) - } +} + +empty_command_response!(Mount); + +impl Command<'_, '_> for Mount { + type Request = MountRequest; + type Response = MountResponse; } diff --git a/src/commands/mounts_and_neighbors/unmount.rs b/src/commands/mounts_and_neighbors/unmount.rs index fe85e2d..aff3a01 100644 --- a/src/commands/mounts_and_neighbors/unmount.rs +++ b/src/commands/mounts_and_neighbors/unmount.rs @@ -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 { - 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 { + 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("") + ) + } + + fn parse(mut parts: RequestTokenizer<'_>) -> Result { + let item_token = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let item = item_token + .parse::() + .map_err(|_| RequestParserError::SyntaxError(0, item_token.to_owned()))?; debug_assert!(parts.next().is_none()); - Ok(path) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) + Ok(UnmountRequest(item)) } } + +empty_command_response!(Unmount); + +impl Command<'_, '_> for Unmount { + type Request = UnmountRequest; + type Response = UnmountResponse; +} diff --git a/src/commands/music_database.rs b/src/commands/music_database.rs index d44b586..2b01971 100644 --- a/src/commands/music_database.rs +++ b/src/commands/music_database.rs @@ -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::*; diff --git a/src/commands/music_database/albumart.rs b/src/commands/music_database/albumart.rs index df995dd..e2541a5 100644 --- a/src/commands/music_database/albumart.rs +++ b/src/commands/music_database/albumart.rs @@ -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, -} - -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 { + fn from_request_enum(request: crate::Request) -> Option { + 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 { 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> { +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct AlbumArtResponse { + pub size: usize, + pub binary: Vec, +} + +impl CommandResponse<'_> for AlbumArtResponse { + fn into_response_enum(self) -> crate::Response { + todo!() + } + + fn from_response_enum(response: crate::Response) -> Option { + todo!() + } + + fn parse(parts: ResponseAttributes<'_>) -> Result> { 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; +} diff --git a/src/commands/music_database/count.rs b/src/commands/music_database/count.rs index 07e1283..b1a42ef 100644 --- a/src/commands/music_database/count.rs +++ b/src/commands/music_database/count.rs @@ -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, } -#[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 { + 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 { + fn parse(mut parts: RequestTokenizer<'_>) -> Result { 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> { +#[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 { + todo!() + } + + fn parse(parts: ResponseAttributes<'_>) -> Result> { 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; +} diff --git a/src/commands/music_database/find.rs b/src/commands/music_database/find.rs index 75a85e9..bb5f0e8 100644 --- a/src/commands/music_database/find.rs +++ b/src/commands/music_database/find.rs @@ -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, } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct FindResponse(Vec); - -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 { + 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 { + fn parse(mut parts: RequestTokenizer<'_>) -> Result { 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> { +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct FindResponse(Vec); + +impl CommandResponse<'_> for FindResponse { + fn into_response_enum(self) -> crate::Response { + todo!() + } + + fn from_response_enum(response: crate::Response) -> Option { + todo!() + } + + fn parse(parts: ResponseAttributes<'_>) -> Result> { 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; +} diff --git a/src/commands/music_database/findadd.rs b/src/commands/music_database/findadd.rs index 75ce433..14a57c7 100644 --- a/src/commands/music_database/findadd.rs +++ b/src/commands/music_database/findadd.rs @@ -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, } -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 { + 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 { + fn parse(mut parts: RequestTokenizer<'_>) -> Result { 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> { - debug_assert!(parts.is_empty()); - Ok(()) - } +} + +empty_command_response!(FindAdd); + +impl Command<'_, '_> for FindAdd { + type Request = FindAddRequest; + type Response = FindAddResponse; } diff --git a/src/commands/music_database/getfingerprint.rs b/src/commands/music_database/getfingerprint.rs index bff8b58..f892435 100644 --- a/src/commands/music_database/getfingerprint.rs +++ b/src/commands/music_database/getfingerprint.rs @@ -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 { - 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 { + todo!() } - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { + fn parse(parts: ResponseAttributes<'_>) -> Result> { 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; +} diff --git a/src/commands/music_database/list.rs b/src/commands/music_database/list.rs index 96ae689..669a97c 100644 --- a/src/commands/music_database/list.rs +++ b/src/commands/music_database/list.rs @@ -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, } -pub type ListResponse = Vec; - -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 { + 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 { + fn parse(mut parts: RequestTokenizer<'_>) -> Result { 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> { +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ListResponse(Vec); + +impl CommandResponse<'_> for ListResponse { + fn into_response_enum(self) -> crate::Response { + todo!() + } + + fn from_response_enum(response: crate::Response) -> Option { + todo!() + } + + fn parse(parts: ResponseAttributes<'_>) -> Result> { 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::, ResponseParserError>>()?; - Ok(list) + Ok(ListResponse(list)) } } + +impl Command<'_, '_> for List { + type Request = ListRequest; + type Response = ListResponse; +} diff --git a/src/commands/music_database/listall.rs b/src/commands/music_database/listall.rs index 95f3d82..8ef0249 100644 --- a/src/commands/music_database/listall.rs +++ b/src/commands/music_database/listall.rs @@ -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; +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ListAllResponse(Vec); -impl Command for ListAll { - type Request = Option; +impl CommandResponse<'_> for ListAllResponse { + fn into_response_enum(self) -> crate::Response { + todo!() + } + + fn from_response_enum(response: crate::Response) -> Option { + todo!() + } + + fn parse(parts: ResponseAttributes<'_>) -> Result> { + 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 { - 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> { - 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 }) - ]) + ])) ) } } diff --git a/src/commands/music_database/listallinfo.rs b/src/commands/music_database/listallinfo.rs index 6867f56..f513ebb 100644 --- a/src/commands/music_database/listallinfo.rs +++ b/src/commands/music_database/listallinfo.rs @@ -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; +single_optional_item_command_request!(ListAllInfo, "listallinfo", Uri); -impl Command for ListAllInfo { - type Request = Option; +// 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); + +impl CommandResponse<'_> for ListAllInfoResponse { + fn from_response_enum(response: crate::Response) -> Option { + todo!() + } + + fn into_response_enum(self) -> crate::Response { + todo!() + } + + fn parse(parts: ResponseAttributes<'_>) -> Result> { + 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 { - 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> { - 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()) }) - ]), + ])), ) } } diff --git a/src/commands/music_database/listfiles.rs b/src/commands/music_database/listfiles.rs index ab4bf94..d567f24 100644 --- a/src/commands/music_database/listfiles.rs +++ b/src/commands/music_database/listfiles.rs @@ -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; +single_optional_item_command_request!(ListFiles, "listfiles", Uri); -impl Command for ListFiles { - type Request = Option; - type Response = ListFilesResponse; - const COMMAND: &'static str = "listfiles"; +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ListFilesResponse(Vec); - 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 { - 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 { + todo!() } - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - // TODO: debug assert only toplevel dirs - + fn parse(parts: ResponseAttributes<'_>) -> Result> { DbSelectionPrintResponse::parse(parts)? .into_iter() .map(|i| match i { @@ -54,50 +38,12 @@ impl Command for ListFiles { Err(ResponseParserError::UnexpectedProperty("playlist")) } }) - .collect::>>() + .collect::, 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; } diff --git a/src/commands/music_database/lsinfo.rs b/src/commands/music_database/lsinfo.rs index 6fb8e26..6c2f592 100644 --- a/src/commands/music_database/lsinfo.rs +++ b/src/commands/music_database/lsinfo.rs @@ -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; +single_optional_item_command_request!(LsInfo, "lsinfo", Uri); -impl Command for LsInfo { - type Request = Option; +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct LsInfoResponse(Vec); + +impl CommandResponse<'_> for LsInfoResponse { + fn into_response_enum(self) -> crate::Response { + todo!() + } + + fn from_response_enum(response: crate::Response) -> Option { + todo!() + } + + fn parse(parts: ResponseAttributes<'_>) -> Result> { + 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 { - 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> { - // 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()) }) - ]) + ])), ); } } diff --git a/src/commands/music_database/readcomments.rs b/src/commands/music_database/readcomments.rs index 32069e8..07e2ef2 100644 --- a/src/commands/music_database/readcomments.rs +++ b/src/commands/music_database/readcomments.rs @@ -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; +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); - 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 { - 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 { + todo!() } - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { + fn parse(parts: ResponseAttributes<'_>) -> Result> { let parts: HashMap<_, _> = parts.into_map()?; let comments = parts @@ -44,6 +35,11 @@ impl Command for ReadComments { }) .collect::, ResponseParserError>>()?; - Ok(comments) + Ok(ReadCommentsResponse(comments)) } } + +impl Command<'_, '_> for ReadComments { + type Request = ReadCommentsRequest; + type Response = ReadCommentsResponse; +} diff --git a/src/commands/music_database/readpicture.rs b/src/commands/music_database/readpicture.rs index dfbdd96..640ecd6 100644 --- a/src/commands/music_database/readpicture.rs +++ b/src/commands/music_database/readpicture.rs @@ -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, - pub mimetype: Option, -} - -impl Command for ReadPicture { - type Request = ReadPictureRequest; - type Response = Option; +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 { + fn from_request_enum(request: crate::Request) -> Option { + 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 { 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> { +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ReadPictureResponse { + pub size: usize, + pub binary: Vec, + pub mimetype: Option, +} + +impl CommandResponse<'_> for ReadPictureResponse { + fn into_response_enum(self) -> crate::Response { + todo!() + } + + fn from_response_enum(response: crate::Response) -> Option { + todo!() + } + + fn parse(parts: ResponseAttributes<'_>) -> Result> { 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; +} diff --git a/src/commands/music_database/rescan.rs b/src/commands/music_database/rescan.rs index 170552c..cd4bd55 100644 --- a/src/commands/music_database/rescan.rs +++ b/src/commands/music_database/rescan.rs @@ -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; - 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 { - 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 { + todo!() } - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { + fn parse(parts: ResponseAttributes<'_>) -> Result> { 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; +} diff --git a/src/commands/music_database/search.rs b/src/commands/music_database/search.rs index 79a8f14..6108647 100644 --- a/src/commands/music_database/search.rs +++ b/src/commands/music_database/search.rs @@ -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, } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct SearchResponse(Vec); - -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 { + 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 { + fn parse(mut parts: RequestTokenizer<'_>) -> Result { 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> { +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct SearchResponse(Vec); + +impl CommandResponse<'_> for SearchResponse { + fn from_response_enum(response: crate::Response) -> Option { + todo!() + } + + fn into_response_enum(self) -> crate::Response { + todo!() + } + + fn parse(parts: ResponseAttributes<'_>) -> Result> { 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; +} diff --git a/src/commands/music_database/searchadd.rs b/src/commands/music_database/searchadd.rs index 580a653..1272a8c 100644 --- a/src/commands/music_database/searchadd.rs +++ b/src/commands/music_database/searchadd.rs @@ -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, } -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 { + 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 { + fn parse(mut parts: RequestTokenizer<'_>) -> Result { 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> { - debug_assert!(parts.is_empty()); - Ok(()) - } +} + +empty_command_response!(SearchAdd); + +impl Command<'_, '_> for SearchAdd { + type Request = SearchAddRequest; + type Response = SearchAddResponse; } diff --git a/src/commands/music_database/searchaddpl.rs b/src/commands/music_database/searchaddpl.rs index a6edd29..3635c31 100644 --- a/src/commands/music_database/searchaddpl.rs +++ b/src/commands/music_database/searchaddpl.rs @@ -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, } -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 { + 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 { + fn parse(mut parts: RequestTokenizer<'_>) -> Result { let playlist_name = parts .next() .ok_or(RequestParserError::UnexpectedEOF)? @@ -95,11 +113,11 @@ impl Command for SearchAddPl { position, }) } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) - } +} + +empty_command_response!(SearchAddPl); + +impl Command<'_, '_> for SearchAddPl { + type Request = SearchAddPlRequest; + type Response = SearchAddPlResponse; } diff --git a/src/commands/music_database/searchcount.rs b/src/commands/music_database/searchcount.rs index 0261216..9261c64 100644 --- a/src/commands/music_database/searchcount.rs +++ b/src/commands/music_database/searchcount.rs @@ -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, } -#[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 { + 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 { + fn parse(mut parts: RequestTokenizer<'_>) -> Result { 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> { +#[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 { + todo!() + } + + fn into_response_enum(self) -> crate::Response { + todo!() + } + + fn parse(parts: ResponseAttributes<'_>) -> Result> { 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; +} diff --git a/src/commands/music_database/update.rs b/src/commands/music_database/update.rs index ecd9312..5fa2da5 100644 --- a/src/commands/music_database/update.rs +++ b/src/commands/music_database/update.rs @@ -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; - 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 { - 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 { + todo!() } - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { + fn parse(parts: ResponseAttributes<'_>) -> Result> { 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; +} diff --git a/src/commands/partition_commands.rs b/src/commands/partition_commands.rs index 1145ec9..ad6fd12 100644 --- a/src/commands/partition_commands.rs +++ b/src/commands/partition_commands.rs @@ -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::*; diff --git a/src/commands/partition_commands/delpartition.rs b/src/commands/partition_commands/delpartition.rs index f39d160..0d8b3c9 100644 --- a/src/commands/partition_commands/delpartition.rs +++ b/src/commands/partition_commands/delpartition.rs @@ -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 { - let partition = parts - .next() - .ok_or(RequestParserError::UnexpectedEOF)? - .to_string(); - - debug_assert!(parts.next().is_none()); - - Ok(partition) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for DelPartition { + type Request = DelPartitionRequest; + type Response = DelPartitionResponse; } diff --git a/src/commands/partition_commands/listpartitions.rs b/src/commands/partition_commands/listpartitions.rs index 2796f7e..2ec877d 100644 --- a/src/commands/partition_commands/listpartitions.rs +++ b/src/commands/partition_commands/listpartitions.rs @@ -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; +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 { - debug_assert!(parts.next().is_none()); - Ok(()) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - 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) - } } diff --git a/src/commands/partition_commands/moveoutput.rs b/src/commands/partition_commands/moveoutput.rs index 6eb4a8e..a1ea16e 100644 --- a/src/commands/partition_commands/moveoutput.rs +++ b/src/commands/partition_commands/moveoutput.rs @@ -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 { - 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> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for MoveOutput { + type Request = MoveOutputRequest; + type Response = MoveOutputResponse; } diff --git a/src/commands/partition_commands/newpartition.rs b/src/commands/partition_commands/newpartition.rs index 42dff24..d66404b 100644 --- a/src/commands/partition_commands/newpartition.rs +++ b/src/commands/partition_commands/newpartition.rs @@ -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 { - let partition = parts - .next() - .ok_or(RequestParserError::UnexpectedEOF)? - .to_string(); - - debug_assert!(parts.next().is_none()); - - Ok(partition) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for NewPartition { + type Request = NewPartitionRequest; + type Response = NewPartitionResponse; } diff --git a/src/commands/partition_commands/partition.rs b/src/commands/partition_commands/partition.rs index 89bd1eb..5e0a134 100644 --- a/src/commands/partition_commands/partition.rs +++ b/src/commands/partition_commands/partition.rs @@ -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 { - let partition = parts - .next() - .ok_or(RequestParserError::UnexpectedEOF)? - .to_string(); - - debug_assert!(parts.next().is_none()); - - Ok(partition) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for Partition { + type Request = PartitionRequest; + type Response = PartitionResponse; } diff --git a/src/commands/playback_options.rs b/src/commands/playback_options.rs index 2b60ed6..27d0a58 100644 --- a/src/commands/playback_options.rs +++ b/src/commands/playback_options.rs @@ -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::*; diff --git a/src/commands/playback_options/consume.rs b/src/commands/playback_options/consume.rs index 58222c7..66ff082 100644 --- a/src/commands/playback_options/consume.rs +++ b/src/commands/playback_options/consume.rs @@ -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 { - 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> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for Consume { + type Request = ConsumeRequest; + type Response = ConsumeResponse; } diff --git a/src/commands/playback_options/crossfade.rs b/src/commands/playback_options/crossfade.rs index 48558e1..214fec1 100644 --- a/src/commands/playback_options/crossfade.rs +++ b/src/commands/playback_options/crossfade.rs @@ -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 { - let seconds = match parts.next() { - Some(s) => s - .parse::() - .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> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for Crossfade { + type Request = CrossfadeRequest; + type Response = CrossfadeResponse; } diff --git a/src/commands/playback_options/getvol.rs b/src/commands/playback_options/getvol.rs index a81fa57..d9c654e 100644 --- a/src/commands/playback_options/getvol.rs +++ b/src/commands/playback_options/getvol.rs @@ -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 { - debug_assert!(parts.next().is_none()); - Ok(()) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - 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; } diff --git a/src/commands/playback_options/mixrampdb.rs b/src/commands/playback_options/mixrampdb.rs index c2306d0..3484296 100644 --- a/src/commands/playback_options/mixrampdb.rs +++ b/src/commands/playback_options/mixrampdb.rs @@ -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 { - let db = match parts.next() { - Some(s) => s - .parse::() - .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> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for MixRampDb { + type Request = MixRampDbRequest; + type Response = MixRampDbResponse; } diff --git a/src/commands/playback_options/mixrampdelay.rs b/src/commands/playback_options/mixrampdelay.rs index 348820c..45e387e 100644 --- a/src/commands/playback_options/mixrampdelay.rs +++ b/src/commands/playback_options/mixrampdelay.rs @@ -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 { - let seconds = match parts.next() { - Some(s) => s - .parse::() - .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> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for MixRampDelay { + type Request = MixRampDelayRequest; + type Response = MixRampDelayResponse; } diff --git a/src/commands/playback_options/random.rs b/src/commands/playback_options/random.rs index 4f2fb79..65f6cba 100644 --- a/src/commands/playback_options/random.rs +++ b/src/commands/playback_options/random.rs @@ -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 { + 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 { + fn parse(mut parts: RequestTokenizer<'_>) -> Result { 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> { - debug_assert!(parts.is_empty()); - Ok(()) + Ok(RandomRequest(state)) } } + +empty_command_response!(Random); + +impl Command<'_, '_> for Random { + type Request = RandomRequest; + type Response = RandomResponse; +} diff --git a/src/commands/playback_options/repeat.rs b/src/commands/playback_options/repeat.rs index d7e73d2..08087d3 100644 --- a/src/commands/playback_options/repeat.rs +++ b/src/commands/playback_options/repeat.rs @@ -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 { + 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 { + fn parse(mut parts: RequestTokenizer<'_>) -> Result { 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> { - debug_assert!(parts.is_empty()); - Ok(()) + Ok(RepeatRequest(state)) } } + +empty_command_response!(Repeat); + +impl Command<'_, '_> for Repeat { + type Request = RepeatRequest; + type Response = RepeatResponse; +} diff --git a/src/commands/playback_options/replay_gain_mode.rs b/src/commands/playback_options/replay_gain_mode.rs index 5de1da1..895efc4 100644 --- a/src/commands/playback_options/replay_gain_mode.rs +++ b/src/commands/playback_options/replay_gain_mode.rs @@ -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 { - 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> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for ReplayGainMode { + type Request = ReplayGainModeRequest; + type Response = ReplayGainModeResponse; } diff --git a/src/commands/playback_options/replay_gain_status.rs b/src/commands/playback_options/replay_gain_status.rs index 4402ec4..3bd10c3 100644 --- a/src/commands/playback_options/replay_gain_status.rs +++ b/src/commands/playback_options/replay_gain_status.rs @@ -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 { - debug_assert!(parts.next().is_none()); - Ok(()) + fn from_response_enum(response: crate::Response) -> Option { + todo!() } - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { + fn parse(parts: ResponseAttributes<'_>) -> Result> { 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; +} diff --git a/src/commands/playback_options/setvol.rs b/src/commands/playback_options/setvol.rs index 071dfd3..a78ac0d 100644 --- a/src/commands/playback_options/setvol.rs +++ b/src/commands/playback_options/setvol.rs @@ -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 { - 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> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for SetVol { + type Request = SetVolRequest; + type Response = SetVolResponse; } diff --git a/src/commands/playback_options/single.rs b/src/commands/playback_options/single.rs index 3614dd6..45c97a1 100644 --- a/src/commands/playback_options/single.rs +++ b/src/commands/playback_options/single.rs @@ -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 { - 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> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for Single { + type Request = SingleRequest; + type Response = SingleResponse; } diff --git a/src/commands/playback_options/volume.rs b/src/commands/playback_options/volume.rs index 5d8f7c8..c95643e 100644 --- a/src/commands/playback_options/volume.rs +++ b/src/commands/playback_options/volume.rs @@ -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 { - 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> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for Volume { + type Request = VolumeRequest; + type Response = VolumeResponse; } diff --git a/src/commands/querying_mpd_status.rs b/src/commands/querying_mpd_status.rs index ab60e62..1f56111 100644 --- a/src/commands/querying_mpd_status.rs +++ b/src/commands/querying_mpd_status.rs @@ -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::*; diff --git a/src/commands/querying_mpd_status/clearerror.rs b/src/commands/querying_mpd_status/clearerror.rs index 324a43c..c59c8db 100644 --- a/src/commands/querying_mpd_status/clearerror.rs +++ b/src/commands/querying_mpd_status/clearerror.rs @@ -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 { - debug_assert!(parts.next().is_none()); - - Ok(()) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for ClearError { + type Request = ClearErrorRequest; + type Response = ClearErrorResponse; } diff --git a/src/commands/querying_mpd_status/currentsong.rs b/src/commands/querying_mpd_status/currentsong.rs index d8be0a3..5d8329a 100644 --- a/src/commands/querying_mpd_status/currentsong.rs +++ b/src/commands/querying_mpd_status/currentsong.rs @@ -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 { - debug_assert!(parts.next().is_none()); - - Ok(()) + fn from_response_enum(response: crate::Response) -> Option { + todo!() } - fn parse_response( - _parts: ResponseAttributes<'_>, - ) -> Result> { + fn parse(parts: ResponseAttributes<'_>) -> Result> { unimplemented!() } } + +impl Command<'_, '_> for CurrentSong { + type Request = CurrentSongRequest; + type Response = CurrentSongResponse; +} diff --git a/src/commands/querying_mpd_status/idle.rs b/src/commands/querying_mpd_status/idle.rs index dcd841b..d3c4557 100644 --- a/src/commands/querying_mpd_status/idle.rs +++ b/src/commands/querying_mpd_status/idle.rs @@ -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>; +pub struct IdleRequest(Option>); -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 { 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 { + fn parse(parts: RequestTokenizer<'_>) -> Result { + 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> { - debug_assert!(parts.is_empty()); - Ok(()) + result.map(IdleRequest) } } + +empty_command_response!(Idle); + +impl Command<'_, '_> for Idle { + type Request = IdleRequest; + type Response = IdleResponse; +} diff --git a/src/commands/querying_mpd_status/stats.rs b/src/commands/querying_mpd_status/stats.rs index 8099b44..c3f3f34 100644 --- a/src/commands/querying_mpd_status/stats.rs +++ b/src/commands/querying_mpd_status/stats.rs @@ -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, } -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 { - debug_assert!(parts.next().is_none()); - - Ok(()) + fn from_response_enum(response: crate::Response) -> Option { + todo!() } - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { + fn parse(parts: ResponseAttributes<'_>) -> Result> { 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; +} diff --git a/src/commands/querying_mpd_status/status.rs b/src/commands/querying_mpd_status/status.rs index 1518454..1300646 100644 --- a/src/commands/querying_mpd_status/status.rs +++ b/src/commands/querying_mpd_status/status.rs @@ -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, } -#[inline] -fn parse_status_response( - parts: ResponseAttributes<'_>, -) -> Result> { - 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 { + 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> { + 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 = get_and_parse_optional_property!(parts, "song", Text); - let song_id: Option = get_and_parse_optional_property!(parts, "songid", Text); - let next_song: Option = get_and_parse_optional_property!(parts, "nextsong", Text); - let next_song_id: Option = 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 = get_and_parse_optional_property!(parts, "song", Text); + let song_id: Option = get_and_parse_optional_property!(parts, "songid", Text); + let next_song: Option = + get_and_parse_optional_property!(parts, "nextsong", Text); + let next_song_id: Option = + 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 { - debug_assert!(parts.next().is_none()); - - Ok(()) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - 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), diff --git a/src/commands/queue.rs b/src/commands/queue.rs index b7806fe..2d32487 100644 --- a/src/commands/queue.rs +++ b/src/commands/queue.rs @@ -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::*; diff --git a/src/commands/queue/add.rs b/src/commands/queue/add.rs index 51992d3..39957ee 100644 --- a/src/commands/queue/add.rs +++ b/src/commands/queue/add.rs @@ -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, } -impl AddRequest { - pub fn new(uri: Uri, position: Option) -> 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 { + match request { + crate::Request::Add(uri, position) => Some(AddRequest { uri, position }), + _ => None, } } - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { + 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 { 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> { - debug_assert!(parts.is_empty()); - Ok(()) - } +} + +empty_command_response!(Add); + +impl Command<'_, '_> for Add { + type Request = AddRequest; + type Response = AddResponse; } diff --git a/src/commands/queue/addid.rs b/src/commands/queue/addid.rs index acfa147..05541eb 100644 --- a/src/commands/queue/addid.rs +++ b/src/commands/queue/addid.rs @@ -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, } -#[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 { + match request { + crate::Request::AddId(uri, position) => Some(AddIdRequest { uri, position }), + _ => None, } } - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { + 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 { 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> { +#[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 { + todo!() + } + + fn parse(parts: ResponseAttributes<'_>) -> Result> { 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; +} diff --git a/src/commands/queue/addtagid.rs b/src/commands/queue/addtagid.rs index a327fd4..458b42d 100644 --- a/src/commands/queue/addtagid.rs +++ b/src/commands/queue/addtagid.rs @@ -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 { + 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 { + fn parse(mut parts: RequestTokenizer<'_>) -> Result { 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> { - debug_assert!(parts.is_empty()); - Ok(()) - } +} + +empty_command_response!(AddTagId); + +impl Command<'_, '_> for AddTagId { + type Request = AddTagIdRequest; + type Response = AddTagIdResponse; } diff --git a/src/commands/queue/clear.rs b/src/commands/queue/clear.rs index e797701..2e28900 100644 --- a/src/commands/queue/clear.rs +++ b/src/commands/queue/clear.rs @@ -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 { - debug_assert!(parts.next().is_none()); - Ok(()) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for Clear { + type Request = ClearRequest; + type Response = ClearResponse; } diff --git a/src/commands/queue/cleartagid.rs b/src/commands/queue/cleartagid.rs index 848acc0..ca67779 100644 --- a/src/commands/queue/cleartagid.rs +++ b/src/commands/queue/cleartagid.rs @@ -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 { + fn from_request_enum(request: crate::Request) -> Option { + 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 { 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> { - debug_assert!(parts.is_empty()); - Ok(()) - } +} + +empty_command_response!(ClearTagId); + +impl Command<'_, '_> for ClearTagId { + type Request = ClearTagIdRequest; + type Response = ClearTagIdResponse; } diff --git a/src/commands/queue/delete.rs b/src/commands/queue/delete.rs index 9ffc552..2c9e48e 100644 --- a/src/commands/queue/delete.rs +++ b/src/commands/queue/delete.rs @@ -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 { - 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> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for Delete { + type Request = DeleteRequest; + type Response = DeleteResponse; } diff --git a/src/commands/queue/deleteid.rs b/src/commands/queue/deleteid.rs index 8e08298..3273c2a 100644 --- a/src/commands/queue/deleteid.rs +++ b/src/commands/queue/deleteid.rs @@ -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 { - 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> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for DeleteId { + type Request = DeleteIdRequest; + type Response = DeleteIdResponse; } diff --git a/src/commands/queue/move_.rs b/src/commands/queue/move_.rs index 44a3fa3..1878cd0 100644 --- a/src/commands/queue/move_.rs +++ b/src/commands/queue/move_.rs @@ -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 { + fn from_request_enum(request: crate::Request) -> Option { + 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 { 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> { - debug_assert!(parts.is_empty()); - Ok(()) - } +} + +empty_command_response!(Move); + +impl Command<'_, '_> for Move { + type Request = MoveRequest; + type Response = MoveResponse; } diff --git a/src/commands/queue/moveid.rs b/src/commands/queue/moveid.rs index 764e512..7e4ef99 100644 --- a/src/commands/queue/moveid.rs +++ b/src/commands/queue/moveid.rs @@ -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, SongId}, request_tokenizer::RequestTokenizer, }; @@ -14,16 +14,25 @@ pub struct MoveIdRequest { pub to: AbsouluteRelativeSongPosition, } -impl Command for MoveId { - type Request = MoveIdRequest; - type Response = (); +impl CommandRequest<'_> for MoveIdRequest { const COMMAND: &'static str = "moveid"; - fn serialize_request(&self, request: Self::Request) -> String { - format!("{} {} {}", Self::COMMAND, request.id, request.to) + fn into_request_enum(self) -> crate::Request { + crate::Request::MoveId(self.id, self.to) } - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { + fn from_request_enum(request: crate::Request) -> Option { + match request { + crate::Request::MoveId(id, to) => Some(MoveIdRequest { id, to }), + _ => None, + } + } + + fn serialize(&self) -> String { + format!("{} {} {}", Self::COMMAND, self.id, self.to) + } + + fn parse(mut parts: RequestTokenizer<'_>) -> Result { let id = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; let id = id .parse() @@ -38,11 +47,11 @@ impl Command for MoveId { Ok(MoveIdRequest { id, to }) } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) - } +} + +empty_command_response!(MoveId); + +impl Command<'_, '_> for MoveId { + type Request = MoveIdRequest; + type Response = MoveIdResponse; } diff --git a/src/commands/queue/playlist.rs b/src/commands/queue/playlist.rs index 59890ae..73ca6c9 100644 --- a/src/commands/queue/playlist.rs +++ b/src/commands/queue/playlist.rs @@ -1,28 +1,29 @@ use crate::{ - commands::{Command, RequestParserError, ResponseParserError}, - request_tokenizer::RequestTokenizer, + commands::{Command, CommandResponse, ResponseParserError, empty_command_request}, response_tokenizer::ResponseAttributes, }; pub struct Playlist; -impl Command for Playlist { - type Request = (); - type Response = (); - const COMMAND: &'static str = "playlist"; +empty_command_request!(Playlist, "playlist"); - fn serialize_request(&self, _request: Self::Request) -> String { - Self::COMMAND.to_string() +pub struct PlaylistResponse(); + +impl CommandResponse<'_> for PlaylistResponse { + fn into_response_enum(self) -> crate::Response { + todo!() } - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { - debug_assert!(parts.next().is_none()); - Ok(()) + fn from_response_enum(response: crate::Response) -> Option { + todo!() } - fn parse_response( - _parts: ResponseAttributes<'_>, - ) -> Result> { + fn parse(parts: ResponseAttributes<'_>) -> Result> { unimplemented!() } } + +impl Command<'_, '_> for Playlist { + type Request = PlaylistRequest; + type Response = PlaylistResponse; +} diff --git a/src/commands/queue/playlistfind.rs b/src/commands/queue/playlistfind.rs index b210606..b967954 100644 --- a/src/commands/queue/playlistfind.rs +++ b/src/commands/queue/playlistfind.rs @@ -1,11 +1,10 @@ use serde::{Deserialize, Serialize}; use crate::{ - commands::{Command, RequestParserError, ResponseParserError}, + commands::{Command, CommandRequest, RequestParserError, empty_command_response}, common::types::{Sort, WindowRange}, filter::Filter, request_tokenizer::RequestTokenizer, - response_tokenizer::ResponseAttributes, }; pub struct PlaylistFind; @@ -17,23 +16,36 @@ pub struct PlaylistFindRequest { window: Option, } -impl Command for PlaylistFind { - type Request = PlaylistFindRequest; - type Response = (); +impl CommandRequest<'_> for PlaylistFindRequest { const COMMAND: &'static str = "playlistfind"; - 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::PlaylistFind(self.filter, self.sort, self.window) + } + + fn from_request_enum(request: crate::Request) -> Option { + match request { + crate::Request::PlaylistFind(filter, sort, window) => Some(PlaylistFindRequest { + 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 { + fn parse(mut parts: RequestTokenizer<'_>) -> Result { let filter = match parts.next() { Some(f) => { Filter::parse(f).map_err(|_| RequestParserError::SyntaxError(1, f.to_owned()))? @@ -69,10 +81,11 @@ impl Command for PlaylistFind { window, }) } - - fn parse_response( - _parts: ResponseAttributes<'_>, - ) -> Result> { - unimplemented!() - } +} + +empty_command_response!(PlaylistFind); + +impl Command<'_, '_> for PlaylistFind { + type Request = PlaylistFindRequest; + type Response = PlaylistFindResponse; } diff --git a/src/commands/queue/playlistid.rs b/src/commands/queue/playlistid.rs index a742373..c580ecb 100644 --- a/src/commands/queue/playlistid.rs +++ b/src/commands/queue/playlistid.rs @@ -1,35 +1,30 @@ use crate::{ - commands::{Command, RequestParserError, ResponseParserError}, + commands::{Command, CommandResponse, ResponseParserError, single_item_command_request}, common::types::SongId, - request_tokenizer::RequestTokenizer, response_tokenizer::ResponseAttributes, }; pub struct PlaylistId; -impl Command for PlaylistId { - type Request = SongId; - type Response = (); - const COMMAND: &'static str = "playlistid"; +single_item_command_request!(PlaylistId, "playlistid", SongId); - fn serialize_request(&self, request: Self::Request) -> String { - format!("{} {}", Self::COMMAND, request) +pub struct PlaylistIdResponse(); + +impl CommandResponse<'_> for PlaylistIdResponse { + fn into_response_enum(self) -> crate::Response { + todo!() } - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { - 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 from_response_enum(response: crate::Response) -> Option { + todo!() } - fn parse_response( - _parts: ResponseAttributes<'_>, - ) -> Result> { + fn parse(parts: ResponseAttributes<'_>) -> Result> { unimplemented!() } } + +impl Command<'_, '_> for PlaylistId { + type Request = PlaylistIdRequest; + type Response = PlaylistIdResponse; +} diff --git a/src/commands/queue/playlistinfo.rs b/src/commands/queue/playlistinfo.rs index 31890df..870409c 100644 --- a/src/commands/queue/playlistinfo.rs +++ b/src/commands/queue/playlistinfo.rs @@ -1,41 +1,36 @@ +use serde::{Deserialize, Serialize}; + use crate::{ - commands::{Command, RequestParserError, ResponseParserError}, + commands::{ + Command, CommandResponse, RequestParserError, ResponseParserError, + single_optional_item_command_request, + }, common::types::OneOrRange, - request_tokenizer::RequestTokenizer, response_tokenizer::ResponseAttributes, }; pub struct PlaylistInfo; -impl Command for PlaylistInfo { - type Request = Option; - type Response = (); - const COMMAND: &'static str = "playlistinfo"; +single_optional_item_command_request!(PlaylistInfo, "playlistinfo", OneOrRange); - fn serialize_request(&self, request: Self::Request) -> String { - match request { - Some(one_or_range) => format!("{} {}", Self::COMMAND, one_or_range), - None => Self::COMMAND.to_string(), - } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct PlaylistInfoResponse; + +impl CommandResponse<'_> for PlaylistInfoResponse { + fn into_response_enum(self) -> crate::Response { + todo!() } - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { - let one_or_range = parts - .next() - .map(|s| { - s.parse() - .map_err(|_| RequestParserError::SyntaxError(0, s.to_string())) - }) - .transpose()?; - - debug_assert!(parts.next().is_none()); - - Ok(one_or_range) + fn from_response_enum(response: crate::Response) -> Option { + todo!() } - fn parse_response( - _parts: ResponseAttributes<'_>, - ) -> Result> { + fn parse(_parts: ResponseAttributes<'_>) -> Result> { unimplemented!() } } + +impl Command<'_, '_> for PlaylistInfo { + type Request = PlaylistInfoRequest; + type Response = PlaylistInfoResponse; +} diff --git a/src/commands/queue/playlistsearch.rs b/src/commands/queue/playlistsearch.rs index af7d981..844a6f1 100644 --- a/src/commands/queue/playlistsearch.rs +++ b/src/commands/queue/playlistsearch.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; use crate::{ - commands::{Command, RequestParserError, ResponseParserError}, + commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError}, common::types::{Sort, WindowRange}, filter::Filter, request_tokenizer::RequestTokenizer, @@ -17,23 +17,36 @@ pub struct PlaylistSearchRequest { window: Option, } -impl Command for PlaylistSearch { - type Request = PlaylistSearchRequest; - type Response = (); +impl CommandRequest<'_> for PlaylistSearchRequest { const COMMAND: &'static str = "playlistsearch"; - 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::PlaylistSearch(self.filter, self.sort, self.window) + } + + fn from_request_enum(request: crate::Request) -> Option { + match request { + crate::Request::PlaylistSearch(filter, sort, window) => Some(PlaylistSearchRequest { + 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 { + fn parse(mut parts: RequestTokenizer<'_>) -> Result { let filter = match parts.next() { Some(f) => { Filter::parse(f).map_err(|_| RequestParserError::SyntaxError(1, f.to_owned()))? @@ -69,10 +82,26 @@ impl Command for PlaylistSearch { window, }) } +} - fn parse_response( - _parts: ResponseAttributes<'_>, - ) -> Result> { +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct PlaylistSearchResponse; + +impl CommandResponse<'_> for PlaylistSearchResponse { + fn from_response_enum(response: crate::Response) -> Option { + todo!() + } + + fn into_response_enum(self) -> crate::Response { + todo!() + } + + fn parse(parts: ResponseAttributes<'_>) -> Result> { unimplemented!() } } + +impl Command<'_, '_> for PlaylistSearch { + type Request = PlaylistSearchRequest; + type Response = PlaylistSearchResponse; +} diff --git a/src/commands/queue/plchanges.rs b/src/commands/queue/plchanges.rs index b31aa63..e487b16 100644 --- a/src/commands/queue/plchanges.rs +++ b/src/commands/queue/plchanges.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; use crate::{ - commands::{Command, RequestParserError, ResponseParserError}, + commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError}, common::types::{PlaylistVersion, WindowRange}, request_tokenizer::RequestTokenizer, response_tokenizer::ResponseAttributes, @@ -15,19 +15,30 @@ pub struct PlChangesRequest { pub window: Option, } -impl Command for PlChanges { - type Request = PlChangesRequest; - type Response = (); +impl CommandRequest<'_> for PlChangesRequest { const COMMAND: &'static str = "plchanges"; - fn serialize_request(&self, request: Self::Request) -> String { - match request.window { - Some(window) => format!("{} {} {}", Self::COMMAND, request.version, window), - None => format!("{} {}", Self::COMMAND, request.version), + fn into_request_enum(self) -> crate::Request { + crate::Request::PlChanges(self.version, self.window) + } + + fn from_request_enum(request: crate::Request) -> Option { + match request { + crate::Request::PlChanges(version, window) => { + Some(PlChangesRequest { version, window }) + } + _ => None, } } - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { + fn serialize(&self) -> String { + match self.window.as_ref() { + Some(window) => format!("{} {} {}", Self::COMMAND, self.version, window), + None => format!("{} {}", Self::COMMAND, self.version), + } + } + + fn parse(mut parts: RequestTokenizer<'_>) -> Result { let version = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; let version = version .parse() @@ -45,10 +56,26 @@ impl Command for PlChanges { Ok(PlChangesRequest { version, window }) } +} - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct PlChangesResponse; + +impl CommandResponse<'_> for PlChangesResponse { + fn from_response_enum(response: crate::Response) -> Option { + todo!() + } + + fn into_response_enum(self) -> crate::Response { + todo!() + } + + fn parse(parts: ResponseAttributes<'_>) -> Result> { unimplemented!() } } + +impl Command<'_, '_> for PlChanges { + type Request = PlChangesRequest; + type Response = PlChangesResponse; +} diff --git a/src/commands/queue/plchangesposid.rs b/src/commands/queue/plchangesposid.rs index 1d028d5..947e69a 100644 --- a/src/commands/queue/plchangesposid.rs +++ b/src/commands/queue/plchangesposid.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; use crate::{ - commands::{Command, RequestParserError, ResponseParserError}, + commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError}, common::types::{PlaylistVersion, WindowRange}, request_tokenizer::RequestTokenizer, response_tokenizer::ResponseAttributes, @@ -15,19 +15,30 @@ pub struct PlChangesPosIdRequest { pub window: Option, } -impl Command for PlChangesPosId { - type Request = PlChangesPosIdRequest; - type Response = (); +impl CommandRequest<'_> for PlChangesPosIdRequest { const COMMAND: &'static str = "plchangesposid"; - fn serialize_request(&self, request: Self::Request) -> String { - match request.window { - Some(window) => format!("{} {} {}", Self::COMMAND, request.version, window), - None => format!("{} {}", Self::COMMAND, request.version), + fn into_request_enum(self) -> crate::Request { + crate::Request::PlChangesPosId(self.version, self.window) + } + + fn from_request_enum(request: crate::Request) -> Option { + match request { + crate::Request::PlChangesPosId(version, window) => { + Some(PlChangesPosIdRequest { version, window }) + } + _ => None, } } - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { + fn serialize(&self) -> String { + match self.window.as_ref() { + Some(window) => format!("{} {} {}", Self::COMMAND, self.version, window), + None => format!("{} {}", Self::COMMAND, self.version), + } + } + + fn parse(mut parts: RequestTokenizer<'_>) -> Result { let version = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; let version = version .parse() @@ -45,10 +56,26 @@ impl Command for PlChangesPosId { Ok(PlChangesPosIdRequest { version, window }) } +} - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct PlChangesPosIdResponse; + +impl CommandResponse<'_> for PlChangesPosIdResponse { + fn from_response_enum(response: crate::Response) -> Option { + todo!() + } + + fn into_response_enum(self) -> crate::Response { + todo!() + } + + fn parse(parts: ResponseAttributes<'_>) -> Result> { unimplemented!() } } + +impl Command<'_, '_> for PlChangesPosId { + type Request = PlChangesPosIdRequest; + type Response = PlChangesPosIdResponse; +} diff --git a/src/commands/queue/prio.rs b/src/commands/queue/prio.rs index 7d27b8f..7ca818b 100644 --- a/src/commands/queue/prio.rs +++ b/src/commands/queue/prio.rs @@ -1,10 +1,9 @@ use serde::{Deserialize, Serialize}; use crate::{ - commands::{Command, RequestParserError, ResponseParserError}, + commands::{Command, CommandRequest, RequestParserError, empty_command_response}, common::types::{Priority, WindowRange}, request_tokenizer::RequestTokenizer, - response_tokenizer::ResponseAttributes, }; pub struct Prio; @@ -15,16 +14,25 @@ pub struct PrioRequest { pub window: WindowRange, } -impl Command for Prio { - type Request = PrioRequest; - type Response = (); +impl CommandRequest<'_> for PrioRequest { const COMMAND: &'static str = "prio"; - fn serialize_request(&self, request: Self::Request) -> String { - format!("{} {} {}", Self::COMMAND, request.prio, request.window) + fn into_request_enum(self) -> crate::Request { + crate::Request::Prio(self.prio, self.window) } - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { + fn from_request_enum(request: crate::Request) -> Option { + match request { + crate::Request::Prio(prio, window) => Some(PrioRequest { prio, window }), + _ => None, + } + } + + fn serialize(&self) -> String { + format!("{} {} {}", Self::COMMAND, self.prio, self.window) + } + + fn parse(mut parts: RequestTokenizer<'_>) -> Result { let prio = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; let prio = prio .parse() @@ -39,11 +47,11 @@ impl Command for Prio { Ok(PrioRequest { prio, window }) } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) - } +} + +empty_command_response!(Prio); + +impl Command<'_, '_> for Prio { + type Request = PrioRequest; + type Response = PrioResponse; } diff --git a/src/commands/queue/prioid.rs b/src/commands/queue/prioid.rs index 1a58b8d..2503714 100644 --- a/src/commands/queue/prioid.rs +++ b/src/commands/queue/prioid.rs @@ -1,10 +1,9 @@ use serde::{Deserialize, Serialize}; use crate::{ - commands::{Command, RequestParserError, ResponseParserError}, + commands::{Command, CommandRequest, RequestParserError, empty_command_response}, common::types::{Priority, SongId}, request_tokenizer::RequestTokenizer, - response_tokenizer::ResponseAttributes, }; pub struct PrioId; @@ -15,46 +14,54 @@ pub struct PrioIdRequest { pub songids: Vec, } -impl Command for PrioId { - type Request = PrioIdRequest; - type Response = (); +impl CommandRequest<'_> for PrioIdRequest { const COMMAND: &'static str = "prioid"; - fn serialize_request(&self, request: Self::Request) -> String { - let songids = request + fn into_request_enum(self) -> crate::Request { + crate::Request::PrioId(self.prio, self.songids) + } + + fn from_request_enum(request: crate::Request) -> Option { + match request { + crate::Request::PrioId(prio, songids) => Some(PrioIdRequest { prio, songids }), + _ => None, + } + } + + fn serialize(&self) -> String { + let songids = self .songids .iter() .map(|id| id.to_string()) .collect::>() .join(","); - format!("{} {} {}", Self::COMMAND, request.prio, songids) + format!("{} {} {}", Self::COMMAND, self.prio, songids) } - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { + fn parse(mut parts: RequestTokenizer<'_>) -> Result { let prio = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; let prio = prio .parse() .map_err(|_| RequestParserError::SyntaxError(0, prio.to_string()))?; let songids = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; - // TODO: This is just a guess to satisfy compilation, double check it let songids = songids .split(',') .map(|s| { s.parse() .map_err(|_| RequestParserError::SyntaxError(0, s.to_string())) }) - .collect::, RequestParserError>>()?; + .collect::, RequestParserError>>()?; debug_assert!(parts.next().is_none()); Ok(PrioIdRequest { prio, songids }) } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) - } +} + +empty_command_response!(PrioId); + +impl Command<'_, '_> for PrioId { + type Request = PrioIdRequest; + type Response = PrioIdResponse; } diff --git a/src/commands/queue/rangeid.rs b/src/commands/queue/rangeid.rs index f3d4924..b35a5e6 100644 --- a/src/commands/queue/rangeid.rs +++ b/src/commands/queue/rangeid.rs @@ -1,10 +1,9 @@ use serde::{Deserialize, Serialize}; use crate::{ - commands::{Command, RequestParserError, ResponseParserError}, + commands::{Command, CommandRequest, RequestParserError, empty_command_response}, common::types::{SongId, TimeInterval}, request_tokenizer::RequestTokenizer, - response_tokenizer::ResponseAttributes, }; pub struct RangeId; @@ -15,21 +14,28 @@ pub struct RangeIdRequest { time_interval: TimeInterval, } -impl Command for RangeId { - type Request = RangeIdRequest; - type Response = (); +impl CommandRequest<'_> for RangeIdRequest { const COMMAND: &'static str = "rangeid"; - fn serialize_request(&self, request: Self::Request) -> String { - format!( - "{} {} {}", - Self::COMMAND, - request.songid, - request.time_interval - ) + fn into_request_enum(self) -> crate::Request { + crate::Request::RangeId(self.songid, self.time_interval) } - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { + fn from_request_enum(request: crate::Request) -> Option { + match request { + crate::Request::RangeId(songid, time_interval) => Some(RangeIdRequest { + songid, + time_interval, + }), + _ => None, + } + } + + fn serialize(&self) -> String { + format!("{} {} {}", Self::COMMAND, self.songid, self.time_interval) + } + + fn parse(mut parts: RequestTokenizer<'_>) -> Result { let songid = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; let songid = songid .parse() @@ -47,11 +53,11 @@ impl Command for RangeId { time_interval, }) } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) - } +} + +empty_command_response!(RangeId); + +impl Command<'_, '_> for RangeId { + type Request = RangeIdRequest; + type Response = RangeIdResponse; } diff --git a/src/commands/queue/shuffle.rs b/src/commands/queue/shuffle.rs index 5e8702a..91cd014 100644 --- a/src/commands/queue/shuffle.rs +++ b/src/commands/queue/shuffle.rs @@ -1,43 +1,17 @@ use crate::{ - commands::{Command, RequestParserError, ResponseParserError}, + commands::{ + Command, RequestParserError, empty_command_response, single_optional_item_command_request, + }, common::types::OneOrRange, - request_tokenizer::RequestTokenizer, - response_tokenizer::ResponseAttributes, }; pub struct Shuffle; -impl Command for Shuffle { - type Request = Option; - type Response = (); - const COMMAND: &'static str = "shuffle"; +single_optional_item_command_request!(Shuffle, "shuffle", OneOrRange); - fn serialize_request(&self, request: Self::Request) -> String { - match request { - Some(range) => format!("{} {}", Self::COMMAND, range), - None => Self::COMMAND.to_string(), - } - } +empty_command_response!(Shuffle); - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { - let range = parts - .next() - .map(|range| { - range - .parse() - .map_err(|_| RequestParserError::SyntaxError(0, range.to_string())) - }) - .transpose()?; - - debug_assert!(parts.next().is_none()); - - Ok(range) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for Shuffle { + type Request = ShuffleRequest; + type Response = ShuffleResponse; } diff --git a/src/commands/queue/swap.rs b/src/commands/queue/swap.rs index 8128661..39e722a 100644 --- a/src/commands/queue/swap.rs +++ b/src/commands/queue/swap.rs @@ -1,10 +1,9 @@ use serde::{Deserialize, Serialize}; use crate::{ - commands::{Command, RequestParserError, ResponseParserError}, + commands::{Command, CommandRequest, RequestParserError, empty_command_response}, common::types::SongPosition, request_tokenizer::RequestTokenizer, - response_tokenizer::ResponseAttributes, }; pub struct Swap; @@ -15,21 +14,25 @@ pub struct SwapRequest { pub songpos2: SongPosition, } -impl Command for Swap { - type Request = SwapRequest; - type Response = (); +impl CommandRequest<'_> for SwapRequest { const COMMAND: &'static str = "swap"; - fn serialize_request(&self, request: Self::Request) -> String { - format!( - "{} {} {}", - Self::COMMAND, - request.songpos1, - request.songpos2 - ) + fn into_request_enum(self) -> crate::Request { + crate::Request::Swap(self.songpos1, self.songpos2) } - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { + fn from_request_enum(request: crate::Request) -> Option { + match request { + crate::Request::Swap(songpos1, songpos2) => Some(SwapRequest { songpos1, songpos2 }), + _ => None, + } + } + + fn serialize(&self) -> String { + format!("{} {} {}", Self::COMMAND, self.songpos1, self.songpos2) + } + + fn parse(mut parts: RequestTokenizer<'_>) -> Result { let songpos1 = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; let songpos1 = songpos1 .parse() @@ -44,11 +47,11 @@ impl Command for Swap { Ok(SwapRequest { songpos1, songpos2 }) } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) - } +} + +empty_command_response!(Swap); + +impl Command<'_, '_> for Swap { + type Request = SwapRequest; + type Response = SwapResponse; } diff --git a/src/commands/queue/swapid.rs b/src/commands/queue/swapid.rs index 2247485..6c1c9a0 100644 --- a/src/commands/queue/swapid.rs +++ b/src/commands/queue/swapid.rs @@ -1,10 +1,9 @@ use serde::{Deserialize, Serialize}; use crate::{ - commands::{Command, RequestParserError, ResponseParserError}, + commands::{Command, CommandRequest, RequestParserError, empty_command_response}, common::types::SongId, request_tokenizer::RequestTokenizer, - response_tokenizer::ResponseAttributes, }; pub struct SwapId; @@ -15,16 +14,25 @@ pub struct SwapIdRequest { pub songid2: SongId, } -impl Command for SwapId { - type Request = SwapIdRequest; - type Response = (); +impl CommandRequest<'_> for SwapIdRequest { const COMMAND: &'static str = "swapid"; - fn serialize_request(&self, request: Self::Request) -> String { - format!("{} {} {}", Self::COMMAND, request.songid1, request.songid2) + fn into_request_enum(self) -> crate::Request { + crate::Request::SwapId(self.songid1, self.songid2) } - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { + fn from_request_enum(request: crate::Request) -> Option { + match request { + crate::Request::SwapId(songid1, songid2) => Some(SwapIdRequest { songid1, songid2 }), + _ => None, + } + } + + fn serialize(&self) -> String { + format!("{} {} {}", Self::COMMAND, self.songid1, self.songid2) + } + + fn parse(mut parts: RequestTokenizer<'_>) -> Result { let songid1 = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; let songid1 = songid1 .parse() @@ -39,11 +47,11 @@ impl Command for SwapId { Ok(SwapIdRequest { songid1, songid2 }) } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) - } +} + +empty_command_response!(SwapId); + +impl Command<'_, '_> for SwapId { + type Request = SwapIdRequest; + type Response = SwapIdResponse; } diff --git a/src/commands/reflection.rs b/src/commands/reflection.rs index eb7f155..af84d57 100644 --- a/src/commands/reflection.rs +++ b/src/commands/reflection.rs @@ -1,11 +1,11 @@ -pub mod commands; -pub mod config; -pub mod decoders; -pub mod not_commands; -pub mod url_handlers; +mod commands; +mod config; +mod decoders; +mod not_commands; +mod url_handlers; -pub use commands::Commands; -pub use config::Config; -pub use decoders::Decoders; -pub use not_commands::NotCommands; -pub use url_handlers::UrlHandlers; +pub use commands::*; +pub use config::*; +pub use decoders::*; +pub use not_commands::*; +pub use url_handlers::*; diff --git a/src/commands/reflection/commands.rs b/src/commands/reflection/commands.rs index 526d875..e2c0b74 100644 --- a/src/commands/reflection/commands.rs +++ b/src/commands/reflection/commands.rs @@ -1,38 +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 Commands; -pub type CommandsResponse = Vec; +empty_command_request!(Commands, "commands"); -impl Command for Commands { - type Request = (); +multi_item_command_response!(Commands, "command", String); + +impl Command<'_, '_> for Commands { + type Request = CommandsRequest; type Response = CommandsResponse; - const COMMAND: &'static str = "commands"; - - fn serialize_request(&self, _request: Self::Request) -> String { - Self::COMMAND.to_string() - } - - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { - debug_assert!(parts.next().is_none()); - Ok(()) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - let parts: Vec<_> = parts.into_vec()?; - let mut result = Vec::with_capacity(parts.len()); - for (key, value) in parts.into_iter() { - if key != "command" { - return Err(ResponseParserError::UnexpectedProperty(key)); - } - result.push(expect_property_type!(Some(value), "key", Text).to_string()); - } - Ok(result) - } } diff --git a/src/commands/reflection/config.rs b/src/commands/reflection/config.rs index deff963..c86f400 100644 --- a/src/commands/reflection/config.rs +++ b/src/commands/reflection/config.rs @@ -3,13 +3,14 @@ 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_property, get_property}, }; pub struct Config; +empty_command_request!(Config, "config"); + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct ConfigResponse { pub music_directory: String, @@ -17,23 +18,16 @@ pub struct ConfigResponse { pub pcre: bool, } -impl Command for Config { - type Request = (); - type Response = ConfigResponse; - const COMMAND: &'static str = "config"; - - fn serialize_request(&self, _request: Self::Request) -> String { - Self::COMMAND.to_string() +impl CommandResponse<'_> for ConfigResponse { + fn into_response_enum(self) -> crate::Response { + todo!() } - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { - debug_assert!(parts.next().is_none()); - Ok(()) + fn from_response_enum(response: crate::Response) -> Option { + todo!() } - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { + fn parse(parts: ResponseAttributes<'_>) -> Result> { let parts: HashMap<_, _> = parts.into_map()?; let music_directory = get_property!(parts, "music_directory", Text).to_string(); @@ -49,3 +43,8 @@ impl Command for Config { }) } } + +impl Command<'_, '_> for Config { + type Request = ConfigRequest; + type Response = ConfigResponse; +} diff --git a/src/commands/reflection/decoders.rs b/src/commands/reflection/decoders.rs index c1a4ac3..38a80c2 100644 --- a/src/commands/reflection/decoders.rs +++ b/src/commands/reflection/decoders.rs @@ -1,13 +1,14 @@ 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 Decoders; +empty_command_request!(Decoders, "decoders"); + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Decoder { pub plugin: String, @@ -15,25 +16,19 @@ pub struct Decoder { pub mime_types: Vec, } -pub type DecodersResponse = Vec; +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct DecodersResponse(Vec); -impl Command for Decoders { - type Request = (); - type Response = DecodersResponse; - const COMMAND: &'static str = "decoders"; - - fn serialize_request(&self, _request: Self::Request) -> String { - Self::COMMAND.to_string() +impl CommandResponse<'_> for DecodersResponse { + fn into_response_enum(self) -> crate::Response { + todo!() } - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { - debug_assert!(parts.next().is_none()); - Ok(()) + fn from_response_enum(response: crate::Response) -> Option { + todo!() } - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { + fn parse(parts: ResponseAttributes<'_>) -> Result> { let mut result = Vec::new(); let mut current_decoder: Option = None; for (key, value) in parts.into_vec()?.into_iter() { @@ -75,10 +70,15 @@ impl Command for Decoders { result.push(decoder); } - Ok(result) + Ok(DecodersResponse(result)) } } +impl Command<'_, '_> for Decoders { + type Request = DecodersRequest; + type Response = DecodersResponse; +} + #[cfg(test)] mod tests { use indoc::indoc; @@ -106,10 +106,10 @@ mod tests { mime_type: audio/x-mpd-alsa-pcm OK "}; - let result = Decoders::parse_raw_response(input); + let result = Decoders::parse_raw_response(input.as_bytes()); assert_eq!( result, - Ok(vec![ + Ok(DecodersResponse(vec![ Decoder { plugin: "audiofile".to_string(), suffixes: vec![ @@ -137,7 +137,7 @@ mod tests { "audio/x-mpd-alsa-pcm".to_string(), ], }, - ]) + ])), ); } } diff --git a/src/commands/reflection/not_commands.rs b/src/commands/reflection/not_commands.rs index 19399c4..6458c7e 100644 --- a/src/commands/reflection/not_commands.rs +++ b/src/commands/reflection/not_commands.rs @@ -1,39 +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 NotCommands; -pub type NotCommandsResponse = Vec; +empty_command_request!(NotCommands, "notcommands"); -impl Command for NotCommands { - type Request = (); +multi_item_command_response!(NotCommands, "command", String); + +impl Command<'_, '_> for NotCommands { + type Request = NotCommandsRequest; type Response = NotCommandsResponse; - const COMMAND: &'static str = "notcommands"; - - fn serialize_request(&self, _request: Self::Request) -> String { - Self::COMMAND.to_string() - } - - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { - debug_assert!(parts.next().is_none()); - Ok(()) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - let parts: Vec<_> = parts.into_vec()?; - let mut result = Vec::with_capacity(parts.len()); - for (key, value) in parts.into_iter() { - if key != "command" { - return Err(ResponseParserError::UnexpectedProperty(key)); - } - let value = expect_property_type!(Some(value), "command", Text); - result.push(value.to_string()); - } - Ok(result) - } } diff --git a/src/commands/reflection/url_handlers.rs b/src/commands/reflection/url_handlers.rs index fbca5f3..261a3d0 100644 --- a/src/commands/reflection/url_handlers.rs +++ b/src/commands/reflection/url_handlers.rs @@ -1,39 +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 UrlHandlers; -pub type UrlHandlersResponse = Vec; +empty_command_request!(UrlHandlers, "urlhandlers"); -impl Command for UrlHandlers { - type Request = (); +multi_item_command_response!(UrlHandlers, "handler", String); + +impl Command<'_, '_> for UrlHandlers { + type Request = UrlHandlersRequest; type Response = UrlHandlersResponse; - const COMMAND: &'static str = "urlhandlers"; - - fn serialize_request(&self, _request: Self::Request) -> String { - Self::COMMAND.to_string() - } - - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { - debug_assert!(parts.next().is_none()); - Ok(()) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - let parts: Vec<_> = parts.into_vec()?; - let mut url_handlers = Vec::with_capacity(parts.len()); - for (key, value) in parts.into_iter() { - if key != "handler" { - return Err(ResponseParserError::UnexpectedProperty(key)); - } - let value = expect_property_type!(Some(value), "handler", Text); - url_handlers.push(value.to_string()); - } - Ok(url_handlers) - } } diff --git a/src/commands/stickers.rs b/src/commands/stickers.rs index 840d65b..97fc7c0 100644 --- a/src/commands/stickers.rs +++ b/src/commands/stickers.rs @@ -1,21 +1,21 @@ -pub mod sticker_dec; -pub mod sticker_delete; -pub mod sticker_find; -pub mod sticker_get; -pub mod sticker_inc; -pub mod sticker_list; -pub mod sticker_set; -pub mod stickernames; -pub mod stickernamestypes; -pub mod stickertypes; +mod sticker_dec; +mod sticker_delete; +mod sticker_find; +mod sticker_get; +mod sticker_inc; +mod sticker_list; +mod sticker_set; +mod stickernames; +mod stickernamestypes; +mod stickertypes; -pub use sticker_dec::StickerDec; -pub use sticker_delete::StickerDelete; -pub use sticker_find::StickerFind; -pub use sticker_get::StickerGet; -pub use sticker_inc::StickerInc; -pub use sticker_list::StickerList; -pub use sticker_set::StickerSet; -pub use stickernames::StickerNames; -pub use stickernamestypes::StickerNamesTypes; -pub use stickertypes::StickerTypes; +pub use sticker_dec::*; +pub use sticker_delete::*; +pub use sticker_find::*; +pub use sticker_get::*; +pub use sticker_inc::*; +pub use sticker_list::*; +pub use sticker_set::*; +pub use stickernames::*; +pub use stickernamestypes::*; +pub use stickertypes::*; diff --git a/src/commands/stickers/sticker_dec.rs b/src/commands/stickers/sticker_dec.rs index 8bc8537..98d0adb 100644 --- a/src/commands/stickers/sticker_dec.rs +++ b/src/commands/stickers/sticker_dec.rs @@ -1,10 +1,9 @@ use serde::{Deserialize, Serialize}; use crate::{ - commands::{Command, RequestParserError, ResponseParserError}, + commands::{Command, CommandRequest, RequestParserError, empty_command_response}, common::types::{StickerType, Uri}, request_tokenizer::RequestTokenizer, - response_tokenizer::ResponseAttributes, }; pub struct StickerDec; @@ -17,23 +16,37 @@ pub struct StickerDecRequest { pub value: usize, } -impl Command for StickerDec { - type Request = StickerDecRequest; - type Response = (); +impl CommandRequest<'_> for StickerDecRequest { const COMMAND: &'static str = "sticker dec"; - fn serialize_request(&self, request: Self::Request) -> String { + fn into_request_enum(self) -> crate::Request { + crate::Request::StickerDec(self.sticker_type, self.uri, self.name, self.value) + } + + fn from_request_enum(request: crate::Request) -> Option { + match request { + crate::Request::StickerDec(sticker_type, uri, name, value) => Some(StickerDecRequest { + sticker_type, + uri, + name, + value, + }), + _ => None, + } + } + + fn serialize(&self) -> String { format!( "{} {} {} {} {}", Self::COMMAND, - request.sticker_type, - request.uri, - request.name, - request.value + self.sticker_type, + self.uri, + self.name, + self.value ) } - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { + fn parse(mut parts: RequestTokenizer<'_>) -> Result { let sticker_type = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; let sticker_type = sticker_type .parse() @@ -63,12 +76,11 @@ impl Command for StickerDec { value, }) } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - - Ok(()) - } +} + +empty_command_response!(StickerDec); + +impl Command<'_, '_> for StickerDec { + type Request = StickerDecRequest; + type Response = StickerDecResponse; } diff --git a/src/commands/stickers/sticker_delete.rs b/src/commands/stickers/sticker_delete.rs index ddbe0be..9c73846 100644 --- a/src/commands/stickers/sticker_delete.rs +++ b/src/commands/stickers/sticker_delete.rs @@ -1,10 +1,9 @@ use serde::{Deserialize, Serialize}; use crate::{ - commands::{Command, RequestParserError, ResponseParserError}, + commands::{Command, CommandRequest, RequestParserError, empty_command_response}, common::types::{StickerType, Uri}, request_tokenizer::RequestTokenizer, - response_tokenizer::ResponseAttributes, }; pub struct StickerDelete; @@ -16,22 +15,35 @@ pub struct StickerDeleteRequest { pub name: String, } -impl Command for StickerDelete { - type Request = StickerDeleteRequest; - type Response = (); +impl CommandRequest<'_> for StickerDeleteRequest { const COMMAND: &'static str = "sticker delete"; - fn serialize_request(&self, request: Self::Request) -> String { + fn into_request_enum(self) -> crate::Request { + crate::Request::StickerDelete(self.sticker_type, self.uri, self.name) + } + + fn from_request_enum(request: crate::Request) -> Option { + match request { + crate::Request::StickerDelete(sticker_type, uri, name) => Some(StickerDeleteRequest { + sticker_type, + uri, + name, + }), + _ => None, + } + } + + fn serialize(&self) -> String { format!( "{} {} {} {}", Self::COMMAND, - request.sticker_type, - request.uri, - request.name + self.sticker_type, + self.uri, + self.name ) } - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { + fn parse(mut parts: RequestTokenizer<'_>) -> Result { let sticker_type = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; let sticker_type = sticker_type .parse() @@ -55,12 +67,11 @@ impl Command for StickerDelete { name, }) } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - - Ok(()) - } +} + +empty_command_response!(StickerDelete); + +impl Command<'_, '_> for StickerDelete { + type Request = StickerDeleteRequest; + type Response = StickerDeleteResponse; } diff --git a/src/commands/stickers/sticker_find.rs b/src/commands/stickers/sticker_find.rs index 274b0de..9c0abec 100644 --- a/src/commands/stickers/sticker_find.rs +++ b/src/commands/stickers/sticker_find.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; use crate::{ - commands::{Command, RequestParserError, ResponseParserError}, + commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError}, common::types::{Sort, StickerType, Uri, WindowRange}, request_tokenizer::RequestTokenizer, response_tokenizer::{ResponseAttributes, expect_property_type}, @@ -18,38 +18,52 @@ pub struct StickerFindRequest { pub window: Option, } -pub type StickerFindResponse = Vec; - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct StickerFindResponseEntry { - pub uri: String, - pub name: String, - pub value: String, -} - -impl Command for StickerFind { - type Request = StickerFindRequest; - type Response = StickerFindResponse; +impl CommandRequest<'_> for StickerFindRequest { const COMMAND: &'static str = "sticker find"; - fn serialize_request(&self, request: Self::Request) -> String { + fn into_request_enum(self) -> crate::Request { + crate::Request::StickerFind( + self.sticker_type, + self.uri, + self.name, + self.sort, + self.window, + ) + } + + fn from_request_enum(request: crate::Request) -> Option { + match request { + crate::Request::StickerFind(sticker_type, uri, name, sort, window) => { + Some(StickerFindRequest { + sticker_type, + uri, + name, + sort, + window, + }) + } + _ => None, + } + } + + fn serialize(&self) -> String { let mut cmd = format!( "{} {} {} {}", Self::COMMAND, - request.sticker_type, - request.uri, - request.name + self.sticker_type, + self.uri, + self.name ); - if let Some(sort) = request.sort { + 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 { + fn parse(mut parts: RequestTokenizer<'_>) -> Result { let sticker_type = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; let sticker_type = sticker_type .parse() @@ -65,9 +79,6 @@ impl Command for StickerFind { .parse() .map_err(|_| RequestParserError::SyntaxError(1, name.to_owned()))?; - // TODO: handle the case for this command as well: - // sticker find {TYPE} {URI} {NAME} = {VALUE} [sort {SORTTYPE}] [window {START:END}] - let mut sort_or_window = parts.next(); let mut sort = None; if let Some("sort") = sort_or_window { @@ -98,10 +109,28 @@ impl Command for StickerFind { window, }) } +} - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct StickerFindResponse(Vec); + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct StickerFindResponseEntry { + pub uri: String, + pub name: String, + pub value: String, +} + +impl CommandResponse<'_> for StickerFindResponse { + fn from_response_enum(response: crate::Response) -> Option { + todo!() + } + + fn into_response_enum(self) -> crate::Response { + todo!() + } + + fn parse(parts: ResponseAttributes<'_>) -> Result> { let parts: Vec<_> = parts.into_vec()?; let mut stickers = Vec::with_capacity(parts.len() / 2); @@ -138,6 +167,11 @@ impl Command for StickerFind { stickers.push(StickerFindResponseEntry { uri, name, value }); } - Ok(stickers) + Ok(StickerFindResponse(stickers)) } } + +impl Command<'_, '_> for StickerFind { + type Request = StickerFindRequest; + type Response = StickerFindResponse; +} diff --git a/src/commands/stickers/sticker_get.rs b/src/commands/stickers/sticker_get.rs index 9810e78..8b9827e 100644 --- a/src/commands/stickers/sticker_get.rs +++ b/src/commands/stickers/sticker_get.rs @@ -1,10 +1,9 @@ use serde::{Deserialize, Serialize}; use crate::{ - commands::{Command, RequestParserError, ResponseParserError}, + commands::{Command, CommandRequest, RequestParserError, single_item_command_response}, common::types::{StickerType, Uri}, request_tokenizer::RequestTokenizer, - response_tokenizer::{ResponseAttributes, get_next_property}, }; pub struct StickerGet; @@ -16,24 +15,35 @@ pub struct StickerGetRequest { pub name: String, } -pub type StickerGetResponse = String; - -impl Command for StickerGet { - type Request = StickerGetRequest; - type Response = StickerGetResponse; +impl CommandRequest<'_> for StickerGetRequest { const COMMAND: &'static str = "sticker get"; - fn serialize_request(&self, request: Self::Request) -> String { + fn into_request_enum(self) -> crate::Request { + crate::Request::StickerGet(self.sticker_type, self.uri, self.name) + } + + fn from_request_enum(request: crate::Request) -> Option { + match request { + crate::Request::StickerGet(sticker_type, uri, name) => Some(StickerGetRequest { + sticker_type, + uri, + name, + }), + _ => None, + } + } + + fn serialize(&self) -> String { format!( "{} {} {} {}", Self::COMMAND, - request.sticker_type, - request.uri, - request.name + self.sticker_type, + self.uri, + self.name ) } - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { + fn parse(mut parts: RequestTokenizer<'_>) -> Result { let sticker_type = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; let sticker_type = sticker_type .parse() @@ -57,15 +67,11 @@ impl Command for StickerGet { name, }) } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - let parts: Vec<_> = parts.into(); - let mut parts = parts.into_iter().peekable(); - let (key, sticker) = get_next_property!(parts, Text); - debug_assert!(parts.peek().is_none()); - debug_assert!(key == "sticker"); - Ok(sticker.to_string()) - } +} + +single_item_command_response!(StickerGet, "string", String); + +impl Command<'_, '_> for StickerGet { + type Request = StickerGetRequest; + type Response = StickerGetResponse; } diff --git a/src/commands/stickers/sticker_inc.rs b/src/commands/stickers/sticker_inc.rs index 68e348a..cd69e39 100644 --- a/src/commands/stickers/sticker_inc.rs +++ b/src/commands/stickers/sticker_inc.rs @@ -1,10 +1,9 @@ use serde::{Deserialize, Serialize}; use crate::{ - commands::{Command, RequestParserError, ResponseParserError}, + commands::{Command, CommandRequest, RequestParserError, empty_command_response}, common::types::{StickerType, Uri}, request_tokenizer::RequestTokenizer, - response_tokenizer::ResponseAttributes, }; pub struct StickerInc; @@ -17,23 +16,37 @@ pub struct StickerIncRequest { pub value: usize, } -impl Command for StickerInc { - type Request = StickerIncRequest; - type Response = (); +impl CommandRequest<'_> for StickerIncRequest { const COMMAND: &'static str = "sticker inc"; - fn serialize_request(&self, request: Self::Request) -> String { + fn into_request_enum(self) -> crate::Request { + crate::Request::StickerInc(self.sticker_type, self.uri, self.name, self.value) + } + + fn from_request_enum(request: crate::Request) -> Option { + match request { + crate::Request::StickerInc(sticker_type, uri, name, value) => Some(StickerIncRequest { + sticker_type, + uri, + name, + value, + }), + _ => None, + } + } + + fn serialize(&self) -> String { format!( "{} {} {} {} {}", Self::COMMAND, - request.sticker_type, - request.uri, - request.name, - request.value + self.sticker_type, + self.uri, + self.name, + self.value ) } - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { + fn parse(mut parts: RequestTokenizer<'_>) -> Result { let sticker_type = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; let sticker_type = sticker_type .parse() @@ -63,12 +76,11 @@ impl Command for StickerInc { value, }) } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - - Ok(()) - } +} + +empty_command_response!(StickerInc); + +impl Command<'_, '_> for StickerInc { + type Request = StickerIncRequest; + type Response = StickerIncResponse; } diff --git a/src/commands/stickers/sticker_list.rs b/src/commands/stickers/sticker_list.rs index 18b2856..e7898bc 100644 --- a/src/commands/stickers/sticker_list.rs +++ b/src/commands/stickers/sticker_list.rs @@ -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::{StickerType, Uri}, request_tokenizer::RequestTokenizer, response_tokenizer::{GenericResponseValue, ResponseAttributes}, @@ -17,18 +17,27 @@ pub struct StickerListRequest { pub uri: Uri, } -pub type StickerListResponse = HashMap; - -impl Command for StickerList { - type Request = StickerListRequest; - type Response = StickerListResponse; +impl CommandRequest<'_> for StickerListRequest { const COMMAND: &'static str = "sticker list"; - fn serialize_request(&self, request: Self::Request) -> String { - format!("{} {} {}", Self::COMMAND, request.sticker_type, request.uri) + fn into_request_enum(self) -> crate::Request { + crate::Request::StickerList(self.sticker_type, self.uri) } - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { + fn from_request_enum(request: crate::Request) -> Option { + match request { + crate::Request::StickerList(sticker_type, uri) => { + Some(StickerListRequest { sticker_type, uri }) + } + _ => None, + } + } + + fn serialize(&self) -> String { + format!("{} {} {}", Self::COMMAND, self.sticker_type, self.uri) + } + + fn parse(mut parts: RequestTokenizer<'_>) -> Result { let sticker_type = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; let sticker_type = sticker_type .parse() @@ -43,10 +52,21 @@ impl Command for StickerList { Ok(StickerListRequest { sticker_type, uri }) } +} - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct StickerListResponse(HashMap); + +impl CommandResponse<'_> for StickerListResponse { + fn into_response_enum(self) -> crate::Response { + todo!() + } + + fn from_response_enum(response: crate::Response) -> Option { + todo!() + } + + fn parse(parts: ResponseAttributes<'_>) -> Result> { let parts: Vec<_> = parts.into_vec()?; if let Some((k, _)) = parts.iter().find(|(k, _)| *k != "sticker") { return Err(ResponseParserError::UnexpectedProperty(k)); @@ -73,5 +93,11 @@ impl Command for StickerList { Ok((key.to_string(), value.to_string())) }) .collect::, ResponseParserError>>() + .map(StickerListResponse) } } + +impl Command<'_, '_> for StickerList { + type Request = StickerListRequest; + type Response = StickerListResponse; +} diff --git a/src/commands/stickers/sticker_set.rs b/src/commands/stickers/sticker_set.rs index 2ed3afb..d4886b9 100644 --- a/src/commands/stickers/sticker_set.rs +++ b/src/commands/stickers/sticker_set.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; use crate::{ - commands::{Command, RequestParserError, ResponseAttributes, ResponseParserError}, + commands::{Command, CommandRequest, RequestParserError, empty_command_response}, common::types::{StickerType, Uri}, request_tokenizer::RequestTokenizer, }; @@ -16,42 +16,53 @@ pub struct StickerSetRequest { pub value: String, } -impl Command for StickerSet { - type Request = StickerSetRequest; - type Response = (); +impl CommandRequest<'_> for StickerSetRequest { const COMMAND: &'static str = "sticker set"; - fn serialize_request(&self, request: Self::Request) -> String { + fn into_request_enum(self) -> crate::Request { + crate::Request::StickerSet(self.sticker_type, self.uri, self.name, self.value) + } + + fn from_request_enum(request: crate::Request) -> Option { + match request { + crate::Request::StickerSet(sticker_type, uri, name, value) => Some(StickerSetRequest { + sticker_type, + uri, + name, + value, + }), + _ => None, + } + } + + fn serialize(&self) -> String { format!( "{} {} {} {} {}", Self::COMMAND, - request.sticker_type, - request.uri, - request.name, - request.value + self.sticker_type, + self.uri, + self.name, + self.value ) } - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { + fn parse(mut parts: RequestTokenizer<'_>) -> Result { let sticker_type = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; let sticker_type = sticker_type .parse() - .map_err(|_| RequestParserError::SyntaxError(1, sticker_type.to_owned()))?; + .map_err(|_| RequestParserError::SyntaxError(0, sticker_type.to_owned()))?; let uri = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; let uri = uri .parse() - .map_err(|_| RequestParserError::SyntaxError(1, uri.to_owned()))?; + .map_err(|_| RequestParserError::SyntaxError(0, uri.to_owned()))?; let name = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; let name = name .parse() - .map_err(|_| RequestParserError::SyntaxError(1, name.to_owned()))?; + .map_err(|_| RequestParserError::SyntaxError(0, name.to_owned()))?; let value = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; - let value = value - .parse() - .map_err(|_| RequestParserError::SyntaxError(1, value.to_owned()))?; debug_assert!(parts.next().is_none()); @@ -59,15 +70,14 @@ impl Command for StickerSet { sticker_type, uri, name, - value, + value: value.to_owned(), }) } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - - Ok(()) - } +} + +empty_command_response!(StickerSet); + +impl Command<'_, '_> for StickerSet { + type Request = StickerSetRequest; + type Response = StickerSetResponse; } diff --git a/src/commands/stickers/stickernames.rs b/src/commands/stickers/stickernames.rs index 9ccc30a..27aeb81 100644 --- a/src/commands/stickers/stickernames.rs +++ b/src/commands/stickers/stickernames.rs @@ -1,41 +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 StickerNames; -pub type StickerNamesResponse = Vec; +empty_command_request!(StickerNames, "stickernames"); -impl Command for StickerNames { - type Request = (); +multi_item_command_response!(StickerNames, "name", String); + +impl Command<'_, '_> for StickerNames { + type Request = StickerNamesRequest; type Response = StickerNamesResponse; - const COMMAND: &'static str = "stickernames"; - - fn serialize_request(&self, _request: Self::Request) -> String { - Self::COMMAND.to_string() - } - - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { - debug_assert!(parts.next().is_none()); - - Ok(()) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - let parts_: Vec<_> = parts.into_vec()?; - if let Some((k, _)) = parts_.iter().find(|(k, _)| *k != "name") { - return Err(ResponseParserError::UnexpectedProperty(k)); - } - - let list = parts_ - .into_iter() - .map(|(k, v)| Ok(expect_property_type!(Some(v), k, Text).to_string())) - .collect::, ResponseParserError>>()?; - - Ok(list) - } } diff --git a/src/commands/stickers/stickernamestypes.rs b/src/commands/stickers/stickernamestypes.rs index a8cdb9f..87daecb 100644 --- a/src/commands/stickers/stickernamestypes.rs +++ b/src/commands/stickers/stickernamestypes.rs @@ -1,7 +1,9 @@ use std::collections::HashMap; +use serde::{Deserialize, Serialize}; + use crate::{ - commands::{Command, RequestParserError, ResponseParserError}, + commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError}, common::types::StickerType, request_tokenizer::RequestTokenizer, response_tokenizer::{ResponseAttributes, expect_property_type}, @@ -9,32 +11,61 @@ use crate::{ pub struct StickerNamesTypes; -pub type StickerNamesTypesResponse = HashMap>; +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct StickerNamesTypesRequest(Option); -impl Command for StickerNamesTypes { - type Request = Option; - type Response = StickerNamesTypesResponse; +impl CommandRequest<'_> for StickerNamesTypesRequest { const COMMAND: &'static str = "stickernamestypes"; - fn serialize_request(&self, request: Self::Request) -> String { - if let Some(sticker_type) = request { + fn into_request_enum(self) -> crate::Request { + crate::Request::StickerNamesTypes(self.0) + } + + fn from_request_enum(request: crate::Request) -> Option { + match request { + crate::Request::StickerNamesTypes(sticker_type) => { + Some(StickerNamesTypesRequest(sticker_type)) + } + _ => None, + } + } + + fn serialize(&self) -> String { + if let Some(sticker_type) = &self.0 { format!("{} {}", Self::COMMAND, sticker_type) } else { Self::COMMAND.to_string() } } - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { - let sticker_type = parts.next().map(|s| s.to_string()); + fn parse(mut parts: RequestTokenizer<'_>) -> Result { + let sticker_type = parts + .next() + .map(|s| s.to_string()) + .map(|s| { + s.parse() + .map_err(|_| RequestParserError::SyntaxError(0, s.to_owned())) + }) + .transpose()?; debug_assert!(parts.next().is_none()); - Ok(sticker_type) + Ok(StickerNamesTypesRequest(sticker_type)) + } +} + +pub struct StickerNamesTypesResponse(HashMap>); + +impl CommandResponse<'_> for StickerNamesTypesResponse { + fn into_response_enum(self) -> crate::Response { + todo!() } - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { + fn from_response_enum(response: crate::Response) -> Option { + todo!() + } + + fn parse(parts: ResponseAttributes<'_>) -> Result> { let parts: Vec<_> = parts.into_vec()?; let mut result = HashMap::new(); @@ -59,6 +90,11 @@ impl Command for StickerNamesTypes { .push(sticker_type); } - Ok(result) + Ok(StickerNamesTypesResponse(result)) } } + +impl Command<'_, '_> for StickerNamesTypes { + type Request = StickerNamesTypesRequest; + type Response = StickerNamesTypesResponse; +} diff --git a/src/commands/stickers/stickertypes.rs b/src/commands/stickers/stickertypes.rs index 1ddbd22..37c0f4c 100644 --- a/src/commands/stickers/stickertypes.rs +++ b/src/commands/stickers/stickertypes.rs @@ -1,41 +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 StickerTypes; -pub type StickerTypesResponse = Vec; +empty_command_request!(StickerTypes, "stickertypes"); -impl Command for StickerTypes { - type Request = (); +multi_item_command_response!(StickerTypes, "stickertype", String); + +impl Command<'_, '_> for StickerTypes { + type Request = StickerTypesRequest; type Response = StickerTypesResponse; - const COMMAND: &'static str = "stickertypes"; - - fn serialize_request(&self, _request: Self::Request) -> String { - Self::COMMAND.to_string() - } - - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { - debug_assert!(parts.next().is_none()); - - Ok(()) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - let parts_: Vec<_> = parts.into_vec()?; - if let Some((k, _)) = parts_.iter().find(|(k, _)| *k != "stickertype") { - return Err(ResponseParserError::UnexpectedProperty(k)); - } - - let list = parts_ - .into_iter() - .map(|(k, v)| Ok(expect_property_type!(Some(v), k, Text).to_string())) - .collect::, ResponseParserError>>()?; - - Ok(list) - } } diff --git a/src/commands/stored_playlists.rs b/src/commands/stored_playlists.rs index 3ac181e..0adfe37 100644 --- a/src/commands/stored_playlists.rs +++ b/src/commands/stored_playlists.rs @@ -1,27 +1,27 @@ -pub mod listplaylist; -pub mod listplaylistinfo; -pub mod listplaylists; -pub mod load; -pub mod playlistadd; -pub mod playlistclear; -pub mod playlistdelete; -pub mod playlistlength; -pub mod playlistmove; -pub mod rename; -pub mod rm; -pub mod save; -pub mod searchplaylist; +mod listplaylist; +mod listplaylistinfo; +mod listplaylists; +mod load; +mod playlistadd; +mod playlistclear; +mod playlistdelete; +mod playlistlength; +mod playlistmove; +mod rename; +mod rm; +mod save; +mod searchplaylist; -pub use listplaylist::ListPlaylist; -pub use listplaylistinfo::ListPlaylistInfo; -pub use listplaylists::ListPlaylists; -pub use load::Load; -pub use playlistadd::PlaylistAdd; -pub use playlistclear::PlaylistClear; -pub use playlistdelete::PlaylistDelete; -pub use playlistlength::PlaylistLength; -pub use playlistmove::PlaylistMove; -pub use rename::Rename; -pub use rm::Rm; -pub use save::Save; -pub use searchplaylist::SearchPlaylist; +pub use listplaylist::*; +pub use listplaylistinfo::*; +pub use listplaylists::*; +pub use load::*; +pub use playlistadd::*; +pub use playlistclear::*; +pub use playlistdelete::*; +pub use playlistlength::*; +pub use playlistmove::*; +pub use rename::*; +pub use rm::*; +pub use save::*; +pub use searchplaylist::*; diff --git a/src/commands/stored_playlists/listplaylist.rs b/src/commands/stored_playlists/listplaylist.rs index 10a8ae5..83d0f1d 100644 --- a/src/commands/stored_playlists/listplaylist.rs +++ b/src/commands/stored_playlists/listplaylist.rs @@ -1,10 +1,15 @@ +use std::path::PathBuf; + use serde::{Deserialize, Serialize}; use crate::{ - commands::{Command, RequestParserError, ResponseParserError}, + commands::{ + Command, CommandRequest, RequestParserError, ResponseParserError, + multi_item_command_response, + }, common::types::{PlaylistName, WindowRange}, request_tokenizer::RequestTokenizer, - response_tokenizer::{ResponseAttributes, expect_property_type}, + response_tokenizer::expect_property_type, }; pub struct ListPlaylist; @@ -15,22 +20,29 @@ pub struct ListPlaylistRequest { range: Option, } -pub type ListPlaylistResponse = Vec; - -impl Command for ListPlaylist { - type Request = ListPlaylistRequest; - type Response = ListPlaylistResponse; +impl CommandRequest<'_> for ListPlaylistRequest { const COMMAND: &'static str = "listplaylist"; - fn serialize_request(&self, request: Self::Request) -> String { - if let Some(range) = request.range { - format!("{} {} {}", Self::COMMAND, request.name, range) - } else { - format!("{} {}", Self::COMMAND, request.name) + fn into_request_enum(self) -> crate::Request { + crate::Request::ListPlaylist(self.name, self.range) + } + + fn from_request_enum(request: crate::Request) -> Option { + match request { + crate::Request::ListPlaylist(name, range) => Some(ListPlaylistRequest { name, range }), + _ => None, } } - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { + fn serialize(&self) -> String { + if let Some(range) = &self.range { + format!("{} {} {}", Self::COMMAND, self.name, range) + } else { + format!("{} {}", Self::COMMAND, self.name) + } + } + + fn parse(mut parts: RequestTokenizer<'_>) -> Result { let name = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; let name = name .parse::() @@ -48,22 +60,11 @@ impl Command for ListPlaylist { Ok(ListPlaylistRequest { name, range }) } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - let parts: Vec<_> = parts.into_vec()?; - let mut files = Vec::with_capacity(parts.len()); - for (key, value) in parts.into_iter() { - if key != "file" { - return Err(ResponseParserError::UnexpectedProperty(key)); - } - - let file = expect_property_type!(Some(value), "file", Text).to_string(); - - files.push(file); - } - - Ok(files) - } +} + +multi_item_command_response!(ListPlaylist, "file", PathBuf); + +impl Command<'_, '_> for ListPlaylist { + type Request = ListPlaylistRequest; + type Response = ListPlaylistResponse; } diff --git a/src/commands/stored_playlists/listplaylistinfo.rs b/src/commands/stored_playlists/listplaylistinfo.rs index 8f5ca06..ed18603 100644 --- a/src/commands/stored_playlists/listplaylistinfo.rs +++ b/src/commands/stored_playlists/listplaylistinfo.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use crate::{ DbSongInfo, Priority, SongId, SongPosition, - commands::{Command, RequestParserError, ResponseParserError}, + commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError}, common::types::{PlaylistName, WindowRange}, request_tokenizer::RequestTokenizer, response_tokenizer::ResponseAttributes, @@ -16,29 +16,30 @@ pub struct ListPlaylistInfoRequest { range: Option, } -pub type ListPlaylistInfoResponse = Vec; - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct ListPlaylistInfoResponseEntry { - position: SongPosition, - id: SongId, - priority: Priority, - song_info: DbSongInfo, -} - -impl Command for ListPlaylistInfo { - type Request = ListPlaylistInfoRequest; - type Response = ListPlaylistInfoResponse; +impl CommandRequest<'_> for ListPlaylistInfoRequest { const COMMAND: &'static str = "listplaylistinfo"; - fn serialize_request(&self, request: Self::Request) -> String { - match request.range { - Some(range) => format!("{} {} {}", Self::COMMAND, request.name, range), - None => format!("{} {}", Self::COMMAND, request.name), + fn into_request_enum(self) -> crate::Request { + crate::Request::ListPlaylistInfo(self.name, self.range) + } + + fn from_request_enum(request: crate::Request) -> Option { + match request { + crate::Request::ListPlaylistInfo(name, range) => { + Some(ListPlaylistInfoRequest { name, range }) + } + _ => None, } } - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { + fn serialize(&self) -> String { + match self.range.as_ref() { + Some(range) => format!("{} {} {}", Self::COMMAND, self.name, range), + None => format!("{} {}", Self::COMMAND, self.name), + } + } + + fn parse(mut parts: RequestTokenizer<'_>) -> Result { let name = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; let name = name .parse::() @@ -56,10 +57,34 @@ impl Command for ListPlaylistInfo { Ok(ListPlaylistInfoRequest { name, range }) } +} - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ListPlaylistInfoResponse(Vec); + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ListPlaylistInfoResponseEntry { + position: SongPosition, + id: SongId, + priority: Priority, + song_info: DbSongInfo, +} + +impl CommandResponse<'_> for ListPlaylistInfoResponse { + fn into_response_enum(self) -> crate::Response { + todo!() + } + + fn from_response_enum(response: crate::Response) -> Option { + todo!() + } + + fn parse(parts: ResponseAttributes<'_>) -> Result> { unimplemented!() } } + +impl Command<'_, '_> for ListPlaylistInfo { + type Request = ListPlaylistInfoRequest; + type Response = ListPlaylistInfoResponse; +} diff --git a/src/commands/stored_playlists/listplaylists.rs b/src/commands/stored_playlists/listplaylists.rs index 9a9ce75..7d1a070 100644 --- a/src/commands/stored_playlists/listplaylists.rs +++ b/src/commands/stored_playlists/listplaylists.rs @@ -2,14 +2,16 @@ use serde::{Deserialize, Serialize}; use crate::{ PlaylistName, - commands::{Command, RequestParserError, ResponseParserError}, - request_tokenizer::RequestTokenizer, + commands::{Command, CommandResponse, ResponseParserError, empty_command_request}, response_tokenizer::{ResponseAttributes, get_next_and_parse_property, get_next_property}, }; pub struct ListPlaylists; -pub type ListPlaylistsResponse = Vec; +empty_command_request!(ListPlaylists, "listplaylists"); + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ListPlaylistsResponse(Vec); #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct ListPlaylistsResponseEntry { @@ -18,23 +20,16 @@ pub struct ListPlaylistsResponseEntry { pub last_modified: Option, } -impl Command for ListPlaylists { - type Request = (); - type Response = ListPlaylistsResponse; - const COMMAND: &'static str = "listplaylists"; - - fn serialize_request(&self, _request: Self::Request) -> String { - Self::COMMAND.to_string() +impl CommandResponse<'_> for ListPlaylistsResponse { + fn into_response_enum(self) -> crate::Response { + todo!() } - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { - debug_assert!(parts.next().is_none()); - Ok(()) + fn from_response_enum(response: crate::Response) -> Option { + todo!() } - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { + fn parse(parts: ResponseAttributes<'_>) -> Result> { let mut parts = parts.into_lazy_vec().into_iter().peekable(); // TODO: count instances of 'playlist' to preallocate let mut result = Vec::new(); @@ -55,10 +50,15 @@ impl Command for ListPlaylists { }); } - Ok(result) + Ok(ListPlaylistsResponse(result)) } } +impl Command<'_, '_> for ListPlaylists { + type Request = ListPlaylistsRequest; + type Response = ListPlaylistsResponse; +} + #[cfg(test)] mod tests { use super::*; @@ -83,11 +83,11 @@ mod tests { OK "}; - let result = ListPlaylists::parse_raw_response(response); + let result = ListPlaylists::parse_raw_response(response.as_bytes()); assert_eq!( result, - Ok(vec![ + Ok(ListPlaylistsResponse(vec![ ListPlaylistsResponseEntry { playlist: "My Playlist 1".into(), last_modified: Some("2024-01-01T12:00:00Z".to_string()), @@ -112,7 +112,7 @@ mod tests { playlist: "My Playlist 6".into(), last_modified: Some("2024-06-01T12:00:00Z".to_string()), }, - ]) + ])), ); } } diff --git a/src/commands/stored_playlists/load.rs b/src/commands/stored_playlists/load.rs index c4ee3ec..a637893 100644 --- a/src/commands/stored_playlists/load.rs +++ b/src/commands/stored_playlists/load.rs @@ -1,10 +1,9 @@ use serde::{Deserialize, Serialize}; use crate::{ - commands::{Command, RequestParserError, ResponseParserError}, + commands::{Command, CommandRequest, RequestParserError, empty_command_response}, common::types::{PlaylistName, SongPosition, WindowRange}, request_tokenizer::RequestTokenizer, - response_tokenizer::ResponseAttributes, }; pub struct Load; @@ -16,29 +15,42 @@ pub struct LoadRequest { position: Option, } -impl Command for Load { - type Request = LoadRequest; - type Response = (); +impl CommandRequest<'_> for LoadRequest { const COMMAND: &'static str = "load"; - fn serialize_request(&self, request: Self::Request) -> String { - match (request.range, request.position) { + fn into_request_enum(self) -> crate::Request { + crate::Request::Load(self.name, self.range, self.position) + } + + fn from_request_enum(request: crate::Request) -> Option { + match request { + crate::Request::Load(name, range, position) => Some(LoadRequest { + name, + range, + position, + }), + _ => None, + } + } + + fn serialize(&self) -> String { + match (self.range.as_ref(), self.position.as_ref()) { (Some(range), Some(pos)) => { - format!("{} {} {} {}", Self::COMMAND, request.name, range, pos) + format!("{} {} {} {}", Self::COMMAND, self.name, range, pos) } (Some(range), None) => { - format!("{} {} {}", Self::COMMAND, request.name, range) + format!("{} {} {}", Self::COMMAND, self.name, range) } (None, Some(pos)) => { - format!("{} {} {}", Self::COMMAND, request.name, pos) + format!("{} {} {}", Self::COMMAND, self.name, pos) } (None, None) => { - format!("{} {}", Self::COMMAND, request.name) + format!("{} {}", Self::COMMAND, self.name) } } } - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { + fn parse(mut parts: RequestTokenizer<'_>) -> Result { let playlist_name = parts .next() .ok_or(RequestParserError::UnexpectedEOF)? @@ -69,11 +81,11 @@ impl Command for Load { position: pos, }) } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) - } +} + +empty_command_response!(Load); + +impl Command<'_, '_> for Load { + type Request = LoadRequest; + type Response = LoadResponse; } diff --git a/src/commands/stored_playlists/playlistadd.rs b/src/commands/stored_playlists/playlistadd.rs index 3ab9a2c..35c8944 100644 --- a/src/commands/stored_playlists/playlistadd.rs +++ b/src/commands/stored_playlists/playlistadd.rs @@ -1,10 +1,9 @@ use serde::{Deserialize, Serialize}; use crate::{ - commands::{Command, RequestParserError, ResponseParserError}, + commands::{Command, CommandRequest, RequestParserError, empty_command_response}, common::types::{PlaylistName, SongPosition, Uri}, request_tokenizer::RequestTokenizer, - response_tokenizer::ResponseAttributes, }; pub struct PlaylistAdd; @@ -16,34 +15,42 @@ pub struct PlaylistAddRequest { pub position: Option, } -impl Command for PlaylistAdd { - type Request = PlaylistAddRequest; - type Response = (); +impl CommandRequest<'_> for PlaylistAddRequest { const COMMAND: &'static str = "playlistadd"; - fn serialize_request(&self, request: Self::Request) -> String { - match request.position { + fn into_request_enum(self) -> crate::Request { + crate::Request::PlaylistAdd(self.playlist_name, self.uri, self.position) + } + + fn from_request_enum(request: crate::Request) -> Option { + match request { + crate::Request::PlaylistAdd(playlist_name, uri, position) => Some(PlaylistAddRequest { + playlist_name, + uri, + position, + }), + _ => None, + } + } + + fn serialize(&self) -> String { + match self.position { Some(pos) => { format!( "{} {} {} {}", Self::COMMAND, - request.playlist_name, - request.uri, + self.playlist_name, + self.uri, pos ) } None => { - format!( - "{} {} {}", - Self::COMMAND, - request.playlist_name, - request.uri - ) + format!("{} {} {}", Self::COMMAND, self.playlist_name, self.uri) } } } - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { + fn parse(mut parts: RequestTokenizer<'_>) -> Result { let playlist_name = parts .next() .ok_or(RequestParserError::UnexpectedEOF)? @@ -70,11 +77,11 @@ impl Command for PlaylistAdd { position, }) } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) - } +} + +empty_command_response!(PlaylistAdd); + +impl Command<'_, '_> for PlaylistAdd { + type Request = PlaylistAddRequest; + type Response = PlaylistAddResponse; } diff --git a/src/commands/stored_playlists/playlistclear.rs b/src/commands/stored_playlists/playlistclear.rs index 06a746a..05bf490 100644 --- a/src/commands/stored_playlists/playlistclear.rs +++ b/src/commands/stored_playlists/playlistclear.rs @@ -1,36 +1,15 @@ use crate::{ - commands::{Command, RequestParserError, ResponseParserError}, + commands::{Command, empty_command_response, single_item_command_request}, common::types::PlaylistName, - request_tokenizer::RequestTokenizer, - response_tokenizer::ResponseAttributes, }; pub struct PlaylistClear; -impl Command for PlaylistClear { - type Request = PlaylistName; - type Response = (); - const COMMAND: &'static str = "playlistclear"; +single_item_command_request!(PlaylistClear, "playlistclear", PlaylistName); - fn serialize_request(&self, request: Self::Request) -> String { - format!("{} {}", Self::COMMAND, request) - } +empty_command_response!(PlaylistClear); - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { - let playlist_name = parts - .next() - .ok_or(RequestParserError::UnexpectedEOF)? - .to_string(); - - debug_assert!(parts.next().is_none()); - - Ok(playlist_name) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for PlaylistClear { + type Request = PlaylistClearRequest; + type Response = PlaylistClearResponse; } diff --git a/src/commands/stored_playlists/playlistdelete.rs b/src/commands/stored_playlists/playlistdelete.rs index cbb48a9..3f907ac 100644 --- a/src/commands/stored_playlists/playlistdelete.rs +++ b/src/commands/stored_playlists/playlistdelete.rs @@ -1,10 +1,9 @@ use serde::{Deserialize, Serialize}; use crate::{ - commands::{Command, RequestParserError, ResponseParserError}, + commands::{Command, CommandRequest, RequestParserError, empty_command_response}, common::types::{OneOrRange, PlaylistName}, request_tokenizer::RequestTokenizer, - response_tokenizer::ResponseAttributes, }; pub struct PlaylistDelete; @@ -15,21 +14,30 @@ pub struct PlaylistDeleteRequest { pub position: OneOrRange, } -impl Command for PlaylistDelete { - type Request = PlaylistDeleteRequest; - type Response = (); +impl CommandRequest<'_> for PlaylistDeleteRequest { const COMMAND: &'static str = "playlistdelete"; - fn serialize_request(&self, request: Self::Request) -> String { - format!( - "{} {} {}", - Self::COMMAND, - request.playlist_name, - request.position - ) + fn into_request_enum(self) -> crate::Request { + crate::Request::PlaylistDelete(self.playlist_name, self.position) } - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { + fn from_request_enum(request: crate::Request) -> Option { + match request { + crate::Request::PlaylistDelete(playlist_name, position) => { + Some(PlaylistDeleteRequest { + playlist_name, + position, + }) + } + _ => None, + } + } + + fn serialize(&self) -> String { + format!("{} {} {}", Self::COMMAND, self.playlist_name, self.position) + } + + fn parse(mut parts: RequestTokenizer<'_>) -> Result { let playlist_name = parts .next() .ok_or(RequestParserError::UnexpectedEOF)? @@ -48,11 +56,11 @@ impl Command for PlaylistDelete { position, }) } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) - } +} + +empty_command_response!(PlaylistDelete); + +impl Command<'_, '_> for PlaylistDelete { + type Request = PlaylistDeleteRequest; + type Response = PlaylistDeleteResponse; } diff --git a/src/commands/stored_playlists/playlistlength.rs b/src/commands/stored_playlists/playlistlength.rs index 892776e..96f5f2c 100644 --- a/src/commands/stored_playlists/playlistlength.rs +++ b/src/commands/stored_playlists/playlistlength.rs @@ -3,43 +3,31 @@ use std::collections::HashMap; use serde::{Deserialize, Serialize}; use crate::{ - commands::{Command, RequestParserError, ResponseParserError}, + commands::{Command, CommandResponse, ResponseParserError, single_item_command_request}, common::types::PlaylistName, - request_tokenizer::RequestTokenizer, response_tokenizer::{ResponseAttributes, get_and_parse_property}, }; pub struct PlaylistLength; +single_item_command_request!(PlaylistLength, "playlistlength", PlaylistName); + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct PlaylistLengthResponse { pub songs: u64, pub playtime: u64, } -impl Command for PlaylistLength { - type Request = PlaylistName; - type Response = PlaylistLengthResponse; - const COMMAND: &'static str = "playlistlength"; - - fn serialize_request(&self, request: Self::Request) -> String { - format!("{} {}", Self::COMMAND, request) +impl CommandResponse<'_> for PlaylistLengthResponse { + fn into_response_enum(self) -> crate::Response { + todo!() } - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { - let playlist_name = parts - .next() - .ok_or(RequestParserError::UnexpectedEOF)? - .to_string(); - - debug_assert!(parts.next().is_none()); - - Ok(playlist_name) + fn from_response_enum(response: crate::Response) -> Option { + todo!() } - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { + fn parse(parts: ResponseAttributes<'_>) -> Result> { let parts: HashMap<_, _> = parts.into_map()?; let songs = get_and_parse_property!(parts, "songs", Text); @@ -48,3 +36,8 @@ impl Command for PlaylistLength { Ok(PlaylistLengthResponse { songs, playtime }) } } + +impl Command<'_, '_> for PlaylistLength { + type Request = PlaylistLengthRequest; + type Response = PlaylistLengthResponse; +} diff --git a/src/commands/stored_playlists/playlistmove.rs b/src/commands/stored_playlists/playlistmove.rs index df53144..01b0092 100644 --- a/src/commands/stored_playlists/playlistmove.rs +++ b/src/commands/stored_playlists/playlistmove.rs @@ -1,10 +1,9 @@ use serde::{Deserialize, Serialize}; use crate::{ - commands::{Command, RequestParserError, ResponseParserError}, + commands::{Command, CommandRequest, RequestParserError, empty_command_response}, common::types::{OneOrRange, PlaylistName, SongPosition}, request_tokenizer::RequestTokenizer, - response_tokenizer::ResponseAttributes, }; pub struct PlaylistMove; @@ -16,29 +15,42 @@ pub struct PlaylistMoveRequest { pub to: SongPosition, } -impl Command for PlaylistMove { - type Request = PlaylistMoveRequest; - type Response = (); +impl CommandRequest<'_> for PlaylistMoveRequest { const COMMAND: &'static str = "playlistmove"; - fn serialize_request(&self, request: Self::Request) -> String { - match request.from { + fn into_request_enum(self) -> crate::Request { + crate::Request::PlaylistMove(self.playlist_name, self.from, self.to) + } + + fn from_request_enum(request: crate::Request) -> Option { + match request { + crate::Request::PlaylistMove(playlist_name, from, to) => Some(PlaylistMoveRequest { + playlist_name, + from, + to, + }), + _ => None, + } + } + + fn serialize(&self) -> String { + match self.from.as_ref() { Some(from) => { format!( "{} {} {} {}", Self::COMMAND, - request.playlist_name, + self.playlist_name, from, - request.to + self.to ) } None => { - format!("{} {} {}", Self::COMMAND, request.playlist_name, request.to) + format!("{} {} {}", Self::COMMAND, self.playlist_name, self.to) } } } - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { + fn parse(mut parts: RequestTokenizer<'_>) -> Result { let playlist_name = parts .next() .ok_or(RequestParserError::UnexpectedEOF)? @@ -69,11 +81,11 @@ impl Command for PlaylistMove { to, }) } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) - } +} + +empty_command_response!(PlaylistMove); + +impl Command<'_, '_> for PlaylistMove { + type Request = PlaylistMoveRequest; + type Response = PlaylistMoveResponse; } diff --git a/src/commands/stored_playlists/rename.rs b/src/commands/stored_playlists/rename.rs index e1579d8..cbb7e6a 100644 --- a/src/commands/stored_playlists/rename.rs +++ b/src/commands/stored_playlists/rename.rs @@ -1,9 +1,8 @@ use serde::{Deserialize, Serialize}; use crate::{ - commands::{Command, RequestParserError, ResponseParserError}, + commands::{Command, CommandRequest, RequestParserError, empty_command_response}, request_tokenizer::RequestTokenizer, - response_tokenizer::ResponseAttributes, }; pub struct Rename; @@ -14,21 +13,27 @@ pub struct RenameRequest { pub new_name: String, } -impl Command for Rename { - type Request = RenameRequest; - type Response = (); +impl CommandRequest<'_> for RenameRequest { const COMMAND: &'static str = "rename"; - fn serialize_request(&self, request: Self::Request) -> String { - format!( - "{} {} {}", - Self::COMMAND, - request.old_name, - request.new_name - ) + fn into_request_enum(self) -> crate::Request { + crate::Request::Rename(self.old_name, self.new_name) } - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { + fn from_request_enum(request: crate::Request) -> Option { + match request { + crate::Request::Rename(old_name, new_name) => { + Some(RenameRequest { old_name, new_name }) + } + _ => None, + } + } + + fn serialize(&self) -> String { + format!("{} {} {}", Self::COMMAND, self.old_name, self.new_name) + } + + fn parse(mut parts: RequestTokenizer<'_>) -> Result { let old_name = parts .next() .ok_or(RequestParserError::UnexpectedEOF)? @@ -43,11 +48,11 @@ impl Command for Rename { Ok(RenameRequest { old_name, new_name }) } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) - } +} + +empty_command_response!(Rename); + +impl Command<'_, '_> for Rename { + type Request = RenameRequest; + type Response = RenameResponse; } diff --git a/src/commands/stored_playlists/rm.rs b/src/commands/stored_playlists/rm.rs index 7032ada..f1a3d2e 100644 --- a/src/commands/stored_playlists/rm.rs +++ b/src/commands/stored_playlists/rm.rs @@ -1,36 +1,15 @@ use crate::{ - commands::{Command, RequestParserError, ResponseParserError}, + commands::{Command, empty_command_response, single_item_command_request}, common::types::PlaylistName, - request_tokenizer::RequestTokenizer, - response_tokenizer::ResponseAttributes, }; pub struct Rm; -impl Command for Rm { - type Request = PlaylistName; - type Response = (); - const COMMAND: &'static str = "rm"; +single_item_command_request!(Rm, "rm", PlaylistName); - fn serialize_request(&self, request: Self::Request) -> String { - format!("{} {}", Self::COMMAND, request) - } +empty_command_response!(Rm); - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { - let playlist_name = parts - .next() - .ok_or(RequestParserError::UnexpectedEOF)? - .to_string(); - - debug_assert!(parts.next().is_none()); - - Ok(playlist_name) - } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) - } +impl Command<'_, '_> for Rm { + type Request = RmRequest; + type Response = RmResponse; } diff --git a/src/commands/stored_playlists/save.rs b/src/commands/stored_playlists/save.rs index f962428..2321074 100644 --- a/src/commands/stored_playlists/save.rs +++ b/src/commands/stored_playlists/save.rs @@ -1,10 +1,9 @@ use serde::{Deserialize, Serialize}; use crate::{ - commands::{Command, RequestParserError, ResponseParserError}, + commands::{Command, CommandRequest, RequestParserError, empty_command_response}, common::types::{PlaylistName, SaveMode}, request_tokenizer::RequestTokenizer, - response_tokenizer::ResponseAttributes, }; pub struct Save; @@ -15,23 +14,35 @@ pub struct SaveRequest { pub mode: Option, } -impl Command for Save { - type Request = SaveRequest; - type Response = (); +impl CommandRequest<'_> for SaveRequest { const COMMAND: &'static str = "save"; - fn serialize_request(&self, request: Self::Request) -> String { - match request.mode { + fn into_request_enum(self) -> crate::Request { + crate::Request::Save(self.playlist_name, self.mode) + } + + fn from_request_enum(request: crate::Request) -> Option { + match request { + crate::Request::Save(playlist_name, mode) => Some(SaveRequest { + playlist_name, + mode, + }), + _ => None, + } + } + + fn serialize(&self) -> String { + match self.mode.as_ref() { Some(mode) => { - format!("{} {} {}", Self::COMMAND, request.playlist_name, mode) + format!("{} {} {}", Self::COMMAND, self.playlist_name, mode) } None => { - format!("{} {}", Self::COMMAND, request.playlist_name) + format!("{} {}", Self::COMMAND, self.playlist_name) } } } - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { + fn parse(mut parts: RequestTokenizer<'_>) -> Result { let playlist_name = parts .next() .ok_or(RequestParserError::UnexpectedEOF)? @@ -52,11 +63,11 @@ impl Command for Save { mode, }) } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - debug_assert!(parts.is_empty()); - Ok(()) - } +} + +empty_command_response!(Save); + +impl Command<'_, '_> for Save { + type Request = SaveRequest; + type Response = SaveResponse; } diff --git a/src/commands/stored_playlists/searchplaylist.rs b/src/commands/stored_playlists/searchplaylist.rs index 72cb6b9..8d60d60 100644 --- a/src/commands/stored_playlists/searchplaylist.rs +++ b/src/commands/stored_playlists/searchplaylist.rs @@ -1,11 +1,10 @@ use serde::{Deserialize, Serialize}; use crate::{ - commands::{Command, RequestParserError, ResponseParserError}, + commands::{Command, CommandRequest, RequestParserError, empty_command_response}, common::types::{PlaylistName, WindowRange}, filter::Filter, request_tokenizer::RequestTokenizer, - response_tokenizer::ResponseAttributes, }; pub struct SearchPlaylist; @@ -17,16 +16,33 @@ pub struct SearchPlaylistRequest { pub range: Option, } -impl Command for SearchPlaylist { - type Request = SearchPlaylistRequest; - type Response = (); +impl CommandRequest<'_> for SearchPlaylistRequest { const COMMAND: &'static str = "searchplaylist"; - fn serialize_request(&self, request: Self::Request) -> String { - unimplemented!() + fn into_request_enum(self) -> crate::Request { + crate::Request::SearchPlaylist(self.name, self.filter, self.range) } - fn parse_request(mut parts: RequestTokenizer<'_>) -> Result { + fn from_request_enum(request: crate::Request) -> Option { + match request { + crate::Request::SearchPlaylist(name, filter, range) => Some(SearchPlaylistRequest { + name, + filter, + range, + }), + _ => None, + } + } + + fn serialize(&self) -> String { + let mut cmd = format!("{} {} {}", Self::COMMAND, self.name, self.filter); + if let Some(range) = &self.range { + cmd.push_str(&format!(" {}", range)); + } + cmd + } + + fn parse(mut parts: RequestTokenizer<'_>) -> Result { let name = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; let name = name .parse::() @@ -55,10 +71,11 @@ impl Command for SearchPlaylist { range, }) } - - fn parse_response( - parts: ResponseAttributes<'_>, - ) -> Result> { - unimplemented!() - } +} + +empty_command_response!(SearchPlaylist); + +impl Command<'_, '_> for SearchPlaylist { + type Request = SearchPlaylistRequest; + type Response = SearchPlaylistResponse; } diff --git a/src/request.rs b/src/request.rs index 8a314fa..2301be8 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,8 +1,10 @@ use serde::{Deserialize, Serialize}; +use crate::commands::*; use crate::common::types::*; use crate::filter::Filter; +use crate::request_tokenizer::RequestTokenizer; // TODO: SingleLineString @@ -202,215 +204,388 @@ pub enum Request { SendMessage(ChannelName, String), } +macro_rules! serialize_req { + ($request_enum:expr, $request_struct:ty) => { + { + if let Some(request) = <$request_struct as CommandRequest<'_>>::from_request_enum($request_enum) { + return <$request_struct as CommandRequest<'_>>::serialize(&request); + } else { + panic!("Mismatched request enum and struct in serialize_request macro, this should never happen!"); + } + } + + }; +} + +macro_rules! parse_req { + ($request_struct:ty, $parts:expr) => { + <$request_struct>::parse($parts).map(|req| req.into_request_enum()) + }; +} + impl Request { - // pub enum RequestParserError { - // SyntaxError(u64, String), - // MissingCommandListEnd(u64), - // NestedCommandList(u64), - // UnexpectedCommandListEnd(u64), - // UnexpectedEOF, - // MissingNewline, - // } + fn serialize(self) -> String { + match self { + /* querying mpd status */ + Request::ClearError => serialize_req!(self, ClearErrorRequest), + Request::CurrentSong => serialize_req!(self, CurrentSongRequest), + Request::Idle(_) => serialize_req!(self, IdleRequest), + Request::Status => serialize_req!(self, StatusRequest), + Request::Stats => serialize_req!(self, StatsRequest), + + // /* playback options */ + Request::Consume(_) => serialize_req!(self, ConsumeRequest), + Request::Crossfade(_) => serialize_req!(self, CrossfadeRequest), + Request::MixRampDb(_) => serialize_req!(self, MixRampDbRequest), + Request::MixRampDelay(_) => serialize_req!(self, MixRampDelayRequest), + Request::Random(_) => serialize_req!(self, RandomRequest), + Request::Repeat(_) => serialize_req!(self, RepeatRequest), + Request::SetVol(_) => serialize_req!(self, SetVolRequest), + Request::GetVol => serialize_req!(self, GetVolRequest), + Request::Single(_) => serialize_req!(self, SingleRequest), + Request::ReplayGainMode(_) => serialize_req!(self, ReplayGainModeRequest), + Request::ReplayGainStatus => serialize_req!(self, ReplayGainStatusRequest), + Request::Volume(_) => serialize_req!(self, VolumeRequest), + + /* playback control */ + Request::Next => serialize_req!(self, NextRequest), + Request::Pause(_) => serialize_req!(self, PauseRequest), + Request::Play(_) => serialize_req!(self, PlayRequest), + Request::PlayId(_) => serialize_req!(self, PlayIdRequest), + Request::Previous => serialize_req!(self, PreviousRequest), + Request::Seek(_, _) => serialize_req!(self, SeekRequest), + Request::SeekId(_, _) => serialize_req!(self, SeekIdRequest), + Request::SeekCur(_, _) => serialize_req!(self, SeekCurRequest), + Request::Stop => serialize_req!(self, StopRequest), + + /* queue */ + Request::Add(_, _) => serialize_req!(self, AddRequest), + Request::AddId(_, _) => serialize_req!(self, AddIdRequest), + Request::Clear => serialize_req!(self, ClearRequest), + Request::Delete(_) => serialize_req!(self, DeleteRequest), + Request::DeleteId(_) => serialize_req!(self, DeleteIdRequest), + Request::Move(_, _) => serialize_req!(self, MoveRequest), + Request::MoveId(_, _) => serialize_req!(self, MoveIdRequest), + Request::Playlist => serialize_req!(self, PlaylistRequest), + Request::PlaylistFind(_, _, _) => serialize_req!(self, PlaylistFindRequest), + Request::PlaylistId(_) => serialize_req!(self, PlaylistIdRequest), + Request::PlaylistInfo(_) => serialize_req!(self, PlaylistInfoRequest), + Request::PlaylistSearch(_, _, _) => serialize_req!(self, PlaylistSearchRequest), + Request::PlChanges(_, _) => serialize_req!(self, PlChangesRequest), + Request::PlChangesPosId(_, _) => serialize_req!(self, PlChangesPosIdRequest), + Request::Prio(_, _) => serialize_req!(self, PrioRequest), + Request::PrioId(_, _) => serialize_req!(self, PrioIdRequest), + Request::RangeId(_, _) => serialize_req!(self, RangeIdRequest), + Request::Shuffle(_) => serialize_req!(self, ShuffleRequest), + Request::Swap(_, _) => serialize_req!(self, SwapRequest), + Request::SwapId(_, _) => serialize_req!(self, SwapIdRequest), + Request::AddTagId(_, _, _) => serialize_req!(self, AddTagIdRequest), + Request::ClearTagId(_, _) => serialize_req!(self, ClearTagIdRequest), + + /* stored playlists */ + Request::ListPlaylist(_, _) => serialize_req!(self, ListPlaylistRequest), + Request::ListPlaylistInfo(_, _) => serialize_req!(self, ListPlaylistInfoRequest), + Request::SearchPlaylist(_, _, _) => serialize_req!(self, SearchPlaylistRequest), + Request::ListPlaylists => serialize_req!(self, ListPlaylistsRequest), + Request::Load(_, _, _) => serialize_req!(self, LoadRequest), + Request::PlaylistAdd(_, _, _) => serialize_req!(self, PlaylistAddRequest), + Request::PlaylistClear(_) => serialize_req!(self, PlaylistClearRequest), + Request::PlaylistDelete(_, _) => serialize_req!(self, PlaylistDeleteRequest), + Request::PlaylistLength(_) => serialize_req!(self, PlaylistLengthRequest), + Request::PlaylistMove(_, _, _) => serialize_req!(self, PlaylistMoveRequest), + Request::Rename(_, _) => serialize_req!(self, RenameRequest), + Request::Rm(_) => serialize_req!(self, RmRequest), + Request::Save(_, _) => serialize_req!(self, SaveRequest), + + /* music database */ + Request::AlbumArt(_, _) => serialize_req!(self, AlbumArtRequest), + Request::Count(_, _) => serialize_req!(self, CountRequest), + Request::GetFingerprint(_) => serialize_req!(self, GetFingerprintRequest), + Request::Find(_, _, _) => serialize_req!(self, FindRequest), + Request::FindAdd(_, _, _, _) => serialize_req!(self, FindAddRequest), + Request::List(_, _, _, _) => serialize_req!(self, ListRequest), + Request::ListAll(_) => serialize_req!(self, ListAllRequest), + Request::ListAllInfo(_) => serialize_req!(self, ListAllInfoRequest), + Request::ListFiles(_) => serialize_req!(self, ListFilesRequest), + Request::LsInfo(_) => serialize_req!(self, LsInfoRequest), + Request::ReadComments(_) => serialize_req!(self, ReadCommentsRequest), + Request::ReadPicture(_, _) => serialize_req!(self, ReadPictureRequest), + Request::Search(_, _, _) => serialize_req!(self, SearchRequest), + Request::SearchAdd(_, _, _, _) => serialize_req!(self, SearchAddRequest), + Request::SearchAddPl(_, _, _, _, _) => serialize_req!(self, SearchAddPlRequest), + Request::SearchCount(_, _) => serialize_req!(self, SearchCountRequest), + Request::Update(_) => serialize_req!(self, UpdateRequest), + Request::Rescan(_) => serialize_req!(self, RescanRequest), + + /* mounts and neighbors */ + Request::Mount(_, _) => serialize_req!(self, MountRequest), + Request::Unmount(_) => serialize_req!(self, UnmountRequest), + Request::ListMounts => serialize_req!(self, ListMountsRequest), + Request::ListNeighbors => serialize_req!(self, ListNeighborsRequest), + + /* stickers */ + Request::StickerGet(_, _, _) => serialize_req!(self, StickerGetRequest), + Request::StickerSet(_, _, _, _) => serialize_req!(self, StickerSetRequest), + Request::StickerInc(_, _, _, _) => serialize_req!(self, StickerIncRequest), + Request::StickerDec(_, _, _, _) => serialize_req!(self, StickerDecRequest), + Request::StickerDelete(_, _, _) => serialize_req!(self, StickerDeleteRequest), + Request::StickerList(_, _) => serialize_req!(self, StickerListRequest), + Request::StickerFind(_, _, _, _, _) => serialize_req!(self, StickerFindRequest), + Request::StickerNames => serialize_req!(self, StickerNamesRequest), + Request::StickerTypes => serialize_req!(self, StickerTypesRequest), + Request::StickerNamesTypes(_) => serialize_req!(self, StickerNamesTypesRequest), + + /* connection settings */ + Request::Close => serialize_req!(self, CloseRequest), + Request::Kill => serialize_req!(self, KillRequest), + Request::Password(_) => serialize_req!(self, PasswordRequest), + Request::Ping => serialize_req!(self, PingRequest), + Request::BinaryLimit(_) => serialize_req!(self, BinaryLimitRequest), + Request::TagTypes => serialize_req!(self, TagTypesRequest), + Request::TagTypesDisable(_) => serialize_req!(self, TagTypesDisableRequest), + Request::TagTypesEnable(_) => serialize_req!(self, TagTypesEnableRequest), + Request::TagTypesClear => serialize_req!(self, TagTypesClearRequest), + Request::TagTypesAll => serialize_req!(self, TagTypesAllRequest), + Request::TagTypesAvailable => serialize_req!(self, TagTypesAvailableRequest), + Request::TagTypesReset(_) => serialize_req!(self, TagTypesResetRequest), + Request::Protocol => serialize_req!(self, ProtocolRequest), + Request::ProtocolDisable(_) => serialize_req!(self, ProtocolDisableRequest), + Request::ProtocolEnable(_) => serialize_req!(self, ProtocolEnableRequest), + Request::ProtocolClear => serialize_req!(self, ProtocolClearRequest), + Request::ProtocolAll => serialize_req!(self, ProtocolAllRequest), + Request::ProtocolAvailable => serialize_req!(self, ProtocolAvailableRequest), + + /* partition commands */ + Request::Partition(_) => serialize_req!(self, PartitionRequest), + Request::ListPartitions => serialize_req!(self, ListPartitionsRequest), + Request::NewPartition(_) => serialize_req!(self, NewPartitionRequest), + Request::DelPartition(_) => serialize_req!(self, DelPartitionRequest), + Request::MoveOutput(_) => serialize_req!(self, MoveOutputRequest), + + /* audio output devices */ + Request::DisableOutput(_) => serialize_req!(self, DisableOutputRequest), + Request::EnableOutput(_) => serialize_req!(self, EnableOutputRequest), + Request::ToggleOutput(_) => serialize_req!(self, ToggleOutputRequest), + Request::Outputs => serialize_req!(self, OutputsRequest), + Request::OutputSet(_, _, _) => serialize_req!(self, OutputSetRequest), + + /* reflection */ + Request::Config => serialize_req!(self, ConfigRequest), + Request::Commands => serialize_req!(self, CommandsRequest), + Request::NotCommands => serialize_req!(self, NotCommandsRequest), + Request::UrlHandlers => serialize_req!(self, UrlHandlersRequest), + Request::Decoders => serialize_req!(self, DecodersRequest), + + /* client to client */ + Request::Subscribe(_) => serialize_req!(self, SubscribeRequest), + Request::Unsubscribe(_) => serialize_req!(self, UnsubscribeRequest), + Request::Channels => serialize_req!(self, ChannelsRequest), + Request::ReadMessages => serialize_req!(self, ReadMessagesRequest), + Request::SendMessage(_, _) => serialize_req!(self, SendMessageRequest), + + _ => unimplemented!(), + } + } // TODO: upon encountering an error, there should be a function that lets you skip to the next OK, // and continue execution. Maybe "parse_next_or_skip(&str) -> RequestParserResponse", which // could skip stuff internally? Or do we want to report back the error with the entire command // and let the library user decide what to do? + // + // TODO: should this return the remaining &str as well? + pub fn parse_next(raw: &str) -> Result { + let (line, rest) = raw + .split_once('\n') + .ok_or(RequestParserError::UnexpectedEOF)?; + let mut parts = RequestTokenizer::new(line); - // impl Request { - // pub fn parse_next(raw: &str) -> Result { - // let (line, rest) = raw - // .split_once('\n') - // .ok_or(RequestParserError::UnexpectedEOF)?; - // let mut parts = RequestTokenizer::new(line); + match parts + .next() + .ok_or(RequestParserError::SyntaxError(0, line.to_string()))? + .trim() + { + "command_list_begin" => { + let mut commands = Vec::new(); + let mut i = 1; + loop { + i += 1; + let (line, _rest) = rest + .split_once('\n') + .ok_or(RequestParserError::MissingCommandListEnd(i))?; + match line.trim() { + "command_list_begin" => { + return Err(RequestParserError::NestedCommandList(i)); + } + "command_list_end" => { + return Ok(Request::CommandList(commands)); + } + input => { + let command = Request::parse_next(input)?; + commands.push(command); + } + } + } + } + "command_list_end" => Err(RequestParserError::UnexpectedCommandListEnd(0)), - // match parts - // .next() - // .ok_or(RequestParserError::SyntaxError(0, line.to_string()))? - // .trim() - // { - // "command_list_begin" => { - // let mut commands = Vec::new(); - // let mut i = 1; - // loop { - // i += 1; - // let (line, rest) = rest - // .split_once('\n') - // .ok_or(RequestParserError::MissingCommandListEnd(i))?; - // match line.trim() { - // "command_list_begin" => { - // return Err(RequestParserError::NestedCommandList(i)); - // } - // "command_list_end" => { - // return Ok((Request::CommandList(commands), rest)); - // } - // input => { - // let (command, _) = Request::parse_next(input)?; - // commands.push(command); - // } - // } - // } - // } - // "command_list_end" => Err(RequestParserError::UnexpectedCommandListEnd(0)), + /* querying mpd status */ + ClearErrorRequest::COMMAND => parse_req!(ClearErrorRequest, parts), + CurrentSongRequest::COMMAND => parse_req!(CurrentSongRequest, parts), + IdleRequest::COMMAND => parse_req!(IdleRequest, parts), + StatusRequest::COMMAND => parse_req!(StatusRequest, parts), + StatsRequest::COMMAND => parse_req!(StatsRequest, parts), - // /* querying mpd status */ - // ClearError::COMMAND => ClearError::parse_request(parts), - // CurrentSong::COMMAND => CurrentSong::parse_request(parts), - // Idle::COMMAND => Idle::parse_request(parts), - // Status::COMMAND => Status::parse_request(parts), - // Stats::COMMAND => Stats::parse_request(parts), + /* playback options */ + ConsumeRequest::COMMAND => parse_req!(ConsumeRequest, parts), + CrossfadeRequest::COMMAND => parse_req!(CrossfadeRequest, parts), + MixRampDbRequest::COMMAND => parse_req!(MixRampDbRequest, parts), + MixRampDelayRequest::COMMAND => parse_req!(MixRampDelayRequest, parts), + RandomRequest::COMMAND => parse_req!(RandomRequest, parts), + RepeatRequest::COMMAND => parse_req!(RepeatRequest, parts), + SetVolRequest::COMMAND => parse_req!(SetVolRequest, parts), + GetVolRequest::COMMAND => parse_req!(GetVolRequest, parts), + SingleRequest::COMMAND => parse_req!(SingleRequest, parts), + ReplayGainModeRequest::COMMAND => parse_req!(ReplayGainModeRequest, parts), + ReplayGainStatusRequest::COMMAND => parse_req!(ReplayGainStatusRequest, parts), + VolumeRequest::COMMAND => parse_req!(VolumeRequest, parts), - // /* playback options */ - // Consume::COMMAND => Consume::parse_request(parts), - // Crossfade::COMMAND => Crossfade::parse_request(parts), - // MixRampDb::COMMAND => MixRampDb::parse_request(parts), - // MixRampDelay::COMMAND => MixRampDelay::parse_request(parts), - // Random::COMMAND => Random::parse_request(parts), - // Repeat::COMMAND => Repeat::parse_request(parts), - // SetVol::COMMAND => SetVol::parse_request(parts), - // GetVol::COMMAND => GetVol::parse_request(parts), - // Single::COMMAND => Single::parse_request(parts), - // ReplayGainMode::COMMAND => ReplayGainMode::parse_request(parts), - // ReplayGainStatus::COMMAND => ReplayGainStatus::parse_request(parts), - // Volume::COMMAND => Volume::parse_request(parts), + /* playback control */ + NextRequest::COMMAND => parse_req!(NextRequest, parts), + PauseRequest::COMMAND => parse_req!(PauseRequest, parts), + PlayRequest::COMMAND => parse_req!(PlayRequest, parts), + PlayIdRequest::COMMAND => parse_req!(PlayIdRequest, parts), + PreviousRequest::COMMAND => parse_req!(PreviousRequest, parts), + SeekRequest::COMMAND => parse_req!(SeekRequest, parts), + SeekIdRequest::COMMAND => parse_req!(SeekIdRequest, parts), + SeekCurRequest::COMMAND => parse_req!(SeekCurRequest, parts), + StopRequest::COMMAND => parse_req!(StopRequest, parts), - // /* playback control */ - // Next::COMMAND => Next::parse_request(parts), - // Pause::COMMAND => Pause::parse_request(parts), - // Play::COMMAND => Play::parse_request(parts), - // PlayId::COMMAND => PlayId::parse_request(parts), - // Previous::COMMAND => Previous::parse_request(parts), - // Seek::COMMAND => Seek::parse_request(parts), - // SeekId::COMMAND => SeekId::parse_request(parts), - // SeekCur::COMMAND => SeekCur::parse_request(parts), - // Stop::COMMAND => Stop::parse_request(parts), + /* queue */ + AddRequest::COMMAND => parse_req!(AddRequest, parts), + AddIdRequest::COMMAND => parse_req!(AddIdRequest, parts), + ClearRequest::COMMAND => parse_req!(ClearRequest, parts), + DeleteRequest::COMMAND => parse_req!(DeleteRequest, parts), + DeleteIdRequest::COMMAND => parse_req!(DeleteIdRequest, parts), + MoveRequest::COMMAND => parse_req!(MoveRequest, parts), + MoveIdRequest::COMMAND => parse_req!(MoveIdRequest, parts), + PlaylistRequest::COMMAND => parse_req!(PlaylistRequest, parts), + PlaylistFindRequest::COMMAND => parse_req!(PlaylistFindRequest, parts), + PlaylistIdRequest::COMMAND => parse_req!(PlaylistIdRequest, parts), + PlaylistInfoRequest::COMMAND => parse_req!(PlaylistInfoRequest, parts), + PlaylistSearchRequest::COMMAND => parse_req!(PlaylistSearchRequest, parts), + PlChangesRequest::COMMAND => parse_req!(PlChangesRequest, parts), + PlChangesPosIdRequest::COMMAND => parse_req!(PlChangesPosIdRequest, parts), + PrioRequest::COMMAND => parse_req!(PrioRequest, parts), + PrioIdRequest::COMMAND => parse_req!(PrioIdRequest, parts), + RangeIdRequest::COMMAND => parse_req!(RangeIdRequest, parts), + ShuffleRequest::COMMAND => parse_req!(ShuffleRequest, parts), + SwapRequest::COMMAND => parse_req!(SwapRequest, parts), + SwapIdRequest::COMMAND => parse_req!(SwapIdRequest, parts), + AddTagIdRequest::COMMAND => parse_req!(AddTagIdRequest, parts), + ClearTagIdRequest::COMMAND => parse_req!(ClearTagIdRequest, parts), - // /* queue */ - // Add::COMMAND => Add::parse_request(parts), - // AddId::COMMAND => AddId::parse_request(parts), - // Clear::COMMAND => Clear::parse_request(parts), - // Delete::COMMAND => Delete::parse_request(parts), - // DeleteId::COMMAND => DeleteId::parse_request(parts), - // Move::COMMAND => Move::parse_request(parts), - // MoveId::COMMAND => MoveId::parse_request(parts), - // Playlist::COMMAND => Playlist::parse_request(parts), - // PlaylistFind::COMMAND => PlaylistFind::parse_request(parts), - // PlaylistId::COMMAND => PlaylistId::parse_request(parts), - // PlaylistInfo::COMMAND => PlaylistInfo::parse_request(parts), - // PlaylistSearch::COMMAND => PlaylistSearch::parse_request(parts), - // PlChanges::COMMAND => PlChanges::parse_request(parts), - // PlChangesPosId::COMMAND => PlChangesPosId::parse_request(parts), - // Prio::COMMAND => Prio::parse_request(parts), - // PrioId::COMMAND => PrioId::parse_request(parts), - // RangeId::COMMAND => RangeId::parse_request(parts), - // Shuffle::COMMAND => Shuffle::parse_request(parts), - // Swap::COMMAND => Swap::parse_request(parts), - // SwapId::COMMAND => SwapId::parse_request(parts), - // AddTagId::COMMAND => AddTagId::parse_request(parts), - // ClearTagId::COMMAND => ClearTagId::parse_request(parts), + /* stored playlists */ + ListPlaylistRequest::COMMAND => parse_req!(ListPlaylistRequest, parts), + ListPlaylistInfoRequest::COMMAND => parse_req!(ListPlaylistInfoRequest, parts), + SearchPlaylistRequest::COMMAND => parse_req!(SearchPlaylistRequest, parts), + ListPlaylistsRequest::COMMAND => parse_req!(ListPlaylistsRequest, parts), + LoadRequest::COMMAND => parse_req!(LoadRequest, parts), + PlaylistAddRequest::COMMAND => parse_req!(PlaylistAddRequest, parts), + PlaylistClearRequest::COMMAND => parse_req!(PlaylistClearRequest, parts), + PlaylistDeleteRequest::COMMAND => parse_req!(PlaylistDeleteRequest, parts), + PlaylistLengthRequest::COMMAND => parse_req!(PlaylistLengthRequest, parts), + PlaylistMoveRequest::COMMAND => parse_req!(PlaylistMoveRequest, parts), + RenameRequest::COMMAND => parse_req!(RenameRequest, parts), + RmRequest::COMMAND => parse_req!(RmRequest, parts), + SaveRequest::COMMAND => parse_req!(SaveRequest, parts), - // /* stored playlists */ - // ListPlaylist::COMMAND => ListPlaylist::parse_request(parts), - // ListPlaylistInfo::COMMAND => ListPlaylistInfo::parse_request(parts), - // SearchPlaylist::COMMAND => SearchPlaylist::parse_request(parts), - // ListPlaylists::COMMAND => ListPlaylists::parse_request(parts), - // Load::COMMAND => Load::parse_request(parts), - // PlaylistAdd::COMMAND => PlaylistAdd::parse_request(parts), - // PlaylistClear::COMMAND => PlaylistClear::parse_request(parts), - // PlaylistDelete::COMMAND => PlaylistDelete::parse_request(parts), - // PlaylistLength::COMMAND => PlaylistLength::parse_request(parts), - // PlaylistMove::COMMAND => PlaylistMove::parse_request(parts), - // Rename::COMMAND => Rename::parse_request(parts), - // Rm::COMMAND => Rm::parse_request(parts), - // Save::COMMAND => Save::parse_request(parts), + /* music database */ + AlbumArtRequest::COMMAND => parse_req!(AlbumArtRequest, parts), + CountRequest::COMMAND => parse_req!(CountRequest, parts), + GetFingerprintRequest::COMMAND => parse_req!(GetFingerprintRequest, parts), + FindRequest::COMMAND => parse_req!(FindRequest, parts), + FindAddRequest::COMMAND => parse_req!(FindAddRequest, parts), + ListRequest::COMMAND => parse_req!(ListRequest, parts), + ListAllRequest::COMMAND => parse_req!(ListAllRequest, parts), + ListAllInfoRequest::COMMAND => parse_req!(ListAllInfoRequest, parts), + ListFilesRequest::COMMAND => parse_req!(ListFilesRequest, parts), + LsInfoRequest::COMMAND => parse_req!(LsInfoRequest, parts), + ReadCommentsRequest::COMMAND => parse_req!(ReadCommentsRequest, parts), + ReadPictureRequest::COMMAND => parse_req!(ReadPictureRequest, parts), + SearchRequest::COMMAND => parse_req!(SearchRequest, parts), + SearchAddRequest::COMMAND => parse_req!(SearchAddRequest, parts), + SearchAddPlRequest::COMMAND => parse_req!(SearchAddPlRequest, parts), + SearchCountRequest::COMMAND => parse_req!(SearchCountRequest, parts), + UpdateRequest::COMMAND => parse_req!(UpdateRequest, parts), + RescanRequest::COMMAND => parse_req!(RescanRequest, parts), - // /* music database */ - // AlbumArt::COMMAND => AlbumArt::parse_request(parts), - // Count::COMMAND => Count::parse_request(parts), - // GetFingerprint::COMMAND => GetFingerprint::parse_request(parts), - // Find::COMMAND => Find::parse_request(parts), - // FindAdd::COMMAND => FindAdd::parse_request(parts), - // List::COMMAND => List::parse_request(parts), - // ListAll::COMMAND => ListAll::parse_request(parts), - // ListAllInfo::COMMAND => ListAllInfo::parse_request(parts), - // ListFiles::COMMAND => ListFiles::parse_request(parts), - // LsInfo::COMMAND => LsInfo::parse_request(parts), - // ReadComments::COMMAND => ReadComments::parse_request(parts), - // ReadPicture::COMMAND => ReadPicture::parse_request(parts), - // Search::COMMAND => Search::parse_request(parts), - // SearchAdd::COMMAND => SearchAdd::parse_request(parts), - // SearchAddPl::COMMAND => SearchAddPl::parse_request(parts), - // SearchCount::COMMAND => SearchCount::parse_request(parts), - // Update::COMMAND => Update::parse_request(parts), - // Rescan::COMMAND => Rescan::parse_request(parts), + /* mounts and neighbors */ + MountRequest::COMMAND => parse_req!(MountRequest, parts), + UnmountRequest::COMMAND => parse_req!(UnmountRequest, parts), + ListMountsRequest::COMMAND => parse_req!(ListMountsRequest, parts), + ListNeighborsRequest::COMMAND => parse_req!(ListNeighborsRequest, parts), - // /* mounts and neighbors */ - // Mount::COMMAND => Mount::parse_request(parts), - // Unmount::COMMAND => Unmount::parse_request(parts), - // ListMounts::COMMAND => ListMounts::parse_request(parts), - // ListNeighbors::COMMAND => ListNeighbors::parse_request(parts), + /* stickers */ + StickerGetRequest::COMMAND => parse_req!(StickerGetRequest, parts), + StickerSetRequest::COMMAND => parse_req!(StickerSetRequest, parts), + StickerIncRequest::COMMAND => parse_req!(StickerIncRequest, parts), + StickerDecRequest::COMMAND => parse_req!(StickerDecRequest, parts), + StickerDeleteRequest::COMMAND => parse_req!(StickerDeleteRequest, parts), + StickerListRequest::COMMAND => parse_req!(StickerListRequest, parts), + StickerFindRequest::COMMAND => parse_req!(StickerFindRequest, parts), + StickerNamesRequest::COMMAND => parse_req!(StickerNamesRequest, parts), + StickerTypesRequest::COMMAND => parse_req!(StickerTypesRequest, parts), + StickerNamesTypesRequest::COMMAND => parse_req!(StickerNamesTypesRequest, parts), - // /* stickers */ - // StickerGet::COMMAND => StickerGet::parse_request(parts), - // StickerSet::COMMAND => StickerSet::parse_request(parts), - // StickerInc::COMMAND => StickerInc::parse_request(parts), - // StickerDec::COMMAND => StickerDec::parse_request(parts), - // StickerDelete::COMMAND => StickerDelete::parse_request(parts), - // StickerList::COMMAND => StickerList::parse_request(parts), - // StickerFind::COMMAND => StickerFind::parse_request(parts), - // StickerNames::COMMAND => StickerNames::parse_request(parts), - // StickerTypes::COMMAND => StickerTypes::parse_request(parts), - // StickerNamesTypes::COMMAND => StickerNamesTypes::parse_request(parts), + /* connection settings */ + CloseRequest::COMMAND => parse_req!(CloseRequest, parts), + KillRequest::COMMAND => parse_req!(KillRequest, parts), + PasswordRequest::COMMAND => parse_req!(PasswordRequest, parts), + PingRequest::COMMAND => parse_req!(PingRequest, parts), + BinaryLimitRequest::COMMAND => parse_req!(BinaryLimitRequest, parts), + TagTypesRequest::COMMAND => parse_req!(TagTypesRequest, parts), + TagTypesDisableRequest::COMMAND => parse_req!(TagTypesDisableRequest, parts), + TagTypesEnableRequest::COMMAND => parse_req!(TagTypesEnableRequest, parts), + TagTypesClearRequest::COMMAND => parse_req!(TagTypesClearRequest, parts), + TagTypesAllRequest::COMMAND => parse_req!(TagTypesAllRequest, parts), + TagTypesAvailableRequest::COMMAND => parse_req!(TagTypesAvailableRequest, parts), + TagTypesResetRequest::COMMAND => parse_req!(TagTypesResetRequest, parts), + ProtocolRequest::COMMAND => parse_req!(ProtocolRequest, parts), + ProtocolDisableRequest::COMMAND => parse_req!(ProtocolDisableRequest, parts), + ProtocolEnableRequest::COMMAND => parse_req!(ProtocolEnableRequest, parts), + ProtocolClearRequest::COMMAND => parse_req!(ProtocolClearRequest, parts), + ProtocolAllRequest::COMMAND => parse_req!(ProtocolAllRequest, parts), + ProtocolAvailableRequest::COMMAND => parse_req!(ProtocolAvailableRequest, parts), - // /* connection settings */ - // Close::COMMAND => Close::parse_request(parts), - // Kill::COMMAND => Kill::parse_request(parts), - // Password::COMMAND => Password::parse_request(parts), - // Ping::COMMAND => Ping::parse_request(parts), - // BinaryLimit::COMMAND => BinaryLimit::parse_request(parts), - // TagTypes::COMMAND => TagTypes::parse_request(parts), - // TagTypesDisable::COMMAND => TagTypesDisable::parse_request(parts), - // TagTypesEnable::COMMAND => TagTypesEnable::parse_request(parts), - // TagTypesClear::COMMAND => TagTypesClear::parse_request(parts), - // TagTypesAll::COMMAND => TagTypesAll::parse_request(parts), - // TagTypesAvailable::COMMAND => TagTypesAvailable::parse_request(parts), - // TagTypesReset::COMMAND => TagTypesReset::parse_request(parts), - // Protocol::COMMAND => Protocol::parse_request(parts), - // ProtocolDisable::COMMAND => ProtocolDisable::parse_request(parts), - // ProtocolEnable::COMMAND => ProtocolEnable::parse_request(parts), - // ProtocolClear::COMMAND => ProtocolClear::parse_request(parts), - // ProtocolAll::COMMAND => ProtocolAll::parse_request(parts), - // ProtocolAvailable::COMMAND => ProtocolAvailable::parse_request(parts), + /* partition commands */ + PartitionRequest::COMMAND => parse_req!(PartitionRequest, parts), + ListPartitionsRequest::COMMAND => parse_req!(ListPartitionsRequest, parts), + NewPartitionRequest::COMMAND => parse_req!(NewPartitionRequest, parts), + DelPartitionRequest::COMMAND => parse_req!(DelPartitionRequest, parts), + MoveOutputRequest::COMMAND => parse_req!(MoveOutputRequest, parts), - // /* partition commands */ - // Partition::COMMAND => Partition::parse_request(parts), - // ListPartitions::COMMAND => ListPartitions::parse_request(parts), - // NewPartition::COMMAND => NewPartition::parse_request(parts), - // DelPartition::COMMAND => DelPartition::parse_request(parts), - // MoveOutput::COMMAND => MoveOutput::parse_request(parts), + /* audio output devices */ + DisableOutputRequest::COMMAND => parse_req!(DisableOutputRequest, parts), + EnableOutputRequest::COMMAND => parse_req!(EnableOutputRequest, parts), + ToggleOutputRequest::COMMAND => parse_req!(ToggleOutputRequest, parts), + OutputsRequest::COMMAND => parse_req!(OutputsRequest, parts), + OutputSetRequest::COMMAND => parse_req!(OutputSetRequest, parts), - // /* audio output devices */ - // DisableOutput::COMMAND => DisableOutput::parse_request(parts), - // EnableOutput::COMMAND => EnableOutput::parse_request(parts), - // ToggleOutput::COMMAND => ToggleOutput::parse_request(parts), - // Outputs::COMMAND => Outputs::parse_request(parts), - // OutputSet::COMMAND => OutputSet::parse_request(parts), + /* reflection */ + ConfigRequest::COMMAND => parse_req!(ConfigRequest, parts), + CommandsRequest::COMMAND => parse_req!(CommandsRequest, parts), + NotCommandsRequest::COMMAND => parse_req!(NotCommandsRequest, parts), + UrlHandlersRequest::COMMAND => parse_req!(UrlHandlersRequest, parts), + DecodersRequest::COMMAND => parse_req!(DecodersRequest, parts), - // /* reflection */ - // Config::COMMAND => Config::parse_request(parts), - // Commands::COMMAND => Commands::parse_request(parts), - // NotCommands::COMMAND => NotCommands::parse_request(parts), - // UrlHandlers::COMMAND => UrlHandlers::parse_request(parts), - // Decoders::COMMAND => Decoders::parse_request(parts), + /* client to client */ + SubscribeRequest::COMMAND => parse_req!(SubscribeRequest, parts), + UnsubscribeRequest::COMMAND => parse_req!(UnsubscribeRequest, parts), + ChannelsRequest::COMMAND => parse_req!(ChannelsRequest, parts), + ReadMessagesRequest::COMMAND => parse_req!(ReadMessagesRequest, parts), + SendMessageRequest::COMMAND => parse_req!(SendMessageRequest, parts), - // /* client to client */ - // Subscribe::COMMAND => Subscribe::parse_request(parts), - // Unsubscribe::COMMAND => Unsubscribe::parse_request(parts), - // Channels::COMMAND => Channels::parse_request(parts), - // ReadMessages::COMMAND => ReadMessages::parse_request(parts), - // SendMessage::COMMAND => SendMessage::parse_request(parts), - - // _ => unimplemented!(), - // } - // } + _ => unimplemented!(), + } + } }