diff --git a/src/commands.rs b/src/commands.rs index 88912ee8..a714bc22 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -57,11 +57,40 @@ where const COMMAND: &'static str; // TODO: add these for ease of throwing parsing errors - // /// The minimum number of arguments this command takes - // const MIN_ARGS: u32; + /// The minimum number of arguments this command takes + const MIN_ARGS: u32; - // /// The maximum number of arguments this command takes - // const MAX_ARGS: Option; + /// The maximum number of arguments this command takes + const MAX_ARGS: Option; + + /// Helper function to create a [`RequestParserError::TooManyArguments`] error + fn too_many_arguments_error(found: u32) -> RequestParserError { + RequestParserError::TooManyArguments { + expected_min: Self::MIN_ARGS, + expected_max: Self::MAX_ARGS, + found, + } + } + + /// Helper function to throw a [`RequestParserError::TooManyArguments`] error + fn throw_if_too_many_arguments(parts: RequestTokenizer<'_>) -> Result<(), RequestParserError> { + let remaining_args = parts.count().try_into().unwrap_or(u32::MAX); + if remaining_args != 0 { + return Err(Self::too_many_arguments_error( + remaining_args.saturating_add(Self::MAX_ARGS.unwrap()), + )); + } + Ok(()) + } + + /// Helper function to create a [`RequestParserError::MissingArguments`] error + fn missing_arguments_error(found: u32) -> RequestParserError { + RequestParserError::MissingArguments { + expected_min: Self::MIN_ARGS, + expected_max: Self::MAX_ARGS, + found, + } + } /// Converts this specific request type to it's corresponding variant in the generic Request enum. fn into_request_enum(self) -> crate::Request; @@ -85,9 +114,11 @@ where fn parse_raw(raw: &str) -> Result { let (line, rest) = raw .split_once('\n') - .ok_or(RequestParserError::UnexpectedEOF)?; + .ok_or(RequestParserError::MissingNewline)?; - debug_assert!(rest.is_empty()); + if line.is_empty() { + return Err(RequestParserError::EmptyLine); + } let mut tokenized = RequestTokenizer::new(line); @@ -239,6 +270,8 @@ macro_rules! empty_command_request { impl crate::commands::CommandRequest for paste::paste! { [<$name Request>] } { const COMMAND: &'static str = $command_name; + const MIN_ARGS: u32 = 0; + const MAX_ARGS: Option = Some(0); fn into_request_enum(self) -> crate::Request { match Self::COMMAND { @@ -261,19 +294,9 @@ macro_rules! empty_command_request { } fn parse( - mut parts: crate::commands::RequestTokenizer<'_>, + parts: crate::commands::RequestTokenizer<'_>, ) -> Result { - if parts.next().is_some() { - return Err(crate::commands::RequestParserError::TooManyArguments { - expected_min: 0, - expected_max: 0, - found: parts - .count() - .try_into() - .unwrap_or(u32::MAX) - .saturating_add(1), - }); - } + Self::throw_if_too_many_arguments(parts)?; Ok(paste::paste! { [<$name Request>] }) } @@ -316,6 +339,8 @@ macro_rules! single_item_command_request { impl crate::commands::CommandRequest for paste::paste! { [<$name Request>] } { const COMMAND: &'static str = $command_name; + const MIN_ARGS: u32 = 1; + const MAX_ARGS: Option = Some(1); fn into_request_enum(self) -> crate::Request { match Self::COMMAND { @@ -340,14 +365,7 @@ macro_rules! single_item_command_request { fn parse( mut parts: crate::commands::RequestTokenizer<'_>, ) -> Result { - let item_token = - parts - .next() - .ok_or(crate::commands::RequestParserError::MissingArguments { - expected_min: 1, - expected_max: 1, - found: 0, - })?; + let item_token = parts.next().ok_or(Self::missing_arguments_error(0))?; let item = item_token.parse::<$item_type>().map_err(|_| { crate::commands::RequestParserError::SubtypeParserError { @@ -357,17 +375,7 @@ macro_rules! single_item_command_request { } })?; - if parts.next().is_some() { - return Err(crate::commands::RequestParserError::TooManyArguments { - expected_min: 1, - expected_max: 1, - found: parts - .count() - .try_into() - .unwrap_or(u32::MAX) - .saturating_add(2), - }); - } + Self::throw_if_too_many_arguments(parts)?; Ok(paste::paste! { [<$name Request>] ( item ) }) } @@ -384,6 +392,8 @@ macro_rules! single_optional_item_command_request { impl crate::commands::CommandRequest for paste::paste! { [<$name Request>] } { const COMMAND: &'static str = $command_name; + const MIN_ARGS: u32 = 0; + const MAX_ARGS: Option = Some(1); fn into_request_enum(self) -> crate::Request { match Self::COMMAND { @@ -424,18 +434,7 @@ macro_rules! single_optional_item_command_request { }) .transpose()?; - if parts.next().is_some() { - let item_count = if item.is_some() { 1 } else { 0 }; - return Err(crate::commands::RequestParserError::TooManyArguments { - expected_min: 0, - expected_max: 1, - found: parts - .count() - .try_into() - .unwrap_or(u32::MAX) - .saturating_add(item_count), - }); - } + Self::throw_if_too_many_arguments(parts)?; Ok(paste::paste! { [<$name Request>] ( item ) }) } @@ -550,6 +549,7 @@ pub enum RequestParserError { #[error("Could not parse the request due to a syntax error at position {0}: {1}")] SyntaxError(u64, String), + // TODO: can we store the parser error as well? #[error( "Could not parse argument {argument_index} of the request (expected type: {expected_type}, raw input: '{raw_input}')" )] @@ -565,28 +565,32 @@ pub enum RequestParserError { }, #[error( - "Too many arguments were provided in the request (expected between {expected_min} and {expected_max}, found {found})" + "Too many arguments were provided in the request (expected between {expected_min} and {expected_max:?}, found {found})" )] TooManyArguments { /// The minimum number of arguments that were expected expected_min: u32, /// The maximum number of arguments that were expected - expected_max: u32, + /// + /// This is `None` if the amount of arguments is unbounded. + expected_max: Option, /// The number of arguments that were found found: u32, }, #[error( - "Not enough arguments were provided in the request (expected between {expected_min} and {expected_max}, found {found})" + "Not enough arguments were provided in the request (expected between {expected_min} and {expected_max:?}, found {found})" )] MissingArguments { /// The minimum number of arguments that were expected expected_min: u32, - /// The maximum number of arguments that were expected - expected_max: u32, + /// The maximum number of arguments that were expected. + /// + /// This is `None` if the amount of arguments is unbounded. + expected_max: Option, /// The number of arguments that were found found: u32, @@ -607,8 +611,9 @@ pub enum RequestParserError { // TODO: remove this, replaced by EmptyLine + MissingArguments #[error("Request ended early, while more arguments were expected")] UnexpectedEOF, - // #[error("Request is missing terminating newline")] - // MissingNewline, + + #[error("Request is missing terminating newline")] + MissingNewline, } // TODO: should these be renamed to fit the mpd docs? diff --git a/src/commands/audio_output_devices/outputset.rs b/src/commands/audio_output_devices/outputset.rs index fe44e9a8..c82a6e90 100644 --- a/src/commands/audio_output_devices/outputset.rs +++ b/src/commands/audio_output_devices/outputset.rs @@ -17,6 +17,8 @@ pub struct OutputSetRequest { impl CommandRequest for OutputSetRequest { const COMMAND: &'static str = "outputset"; + const MIN_ARGS: u32 = 3; + const MAX_ARGS: Option = Some(3); fn into_request_enum(self) -> crate::Request { crate::Request::OutputSet(self.output_id, self.attribute_name, self.attribute_value) @@ -46,14 +48,18 @@ impl CommandRequest for OutputSetRequest { } fn parse(mut parts: RequestTokenizer<'_>) -> Result { - let output_id = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let output_id = parts.next().ok_or(Self::missing_arguments_error(0))?; let output_id = output_id .parse() - .map_err(|_| RequestParserError::SyntaxError(0, output_id.to_owned()))?; - let attribute_name = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; - let attribute_value = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 1, + expected_type: "AudioOutputId".to_string(), + raw_input: output_id.to_string(), + })?; + let attribute_name = parts.next().ok_or(Self::missing_arguments_error(1))?; + let attribute_value = parts.next().ok_or(Self::missing_arguments_error(2))?; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(OutputSetRequest { output_id, diff --git a/src/commands/client_to_client/sendmessage.rs b/src/commands/client_to_client/sendmessage.rs index 864c3ef9..f5e1519c 100644 --- a/src/commands/client_to_client/sendmessage.rs +++ b/src/commands/client_to_client/sendmessage.rs @@ -16,6 +16,8 @@ pub struct SendMessageRequest { impl CommandRequest for SendMessageRequest { const COMMAND: &'static str = "sendmessage"; + const MIN_ARGS: u32 = 2; + const MAX_ARGS: Option = None; fn into_request_enum(self) -> crate::Request { crate::Request::SendMessage(self.channel, self.message) @@ -35,16 +37,18 @@ impl CommandRequest for SendMessageRequest { } fn parse(mut parts: RequestTokenizer<'_>) -> Result { - let channel = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let channel = parts.next().ok_or(Self::missing_arguments_error(0))?; let channel = channel .parse() - .map_err(|_| RequestParserError::SyntaxError(0, channel.to_owned()))?; + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 0, + expected_type: "ChannelName".to_string(), + raw_input: channel.to_string(), + })?; // TODO: SplitWhitespace::remainder() is unstable, use when stable let message = parts.collect::>().join(" "); - debug_assert!(!message.is_empty()); - Ok(SendMessageRequest { channel, message }) } } diff --git a/src/commands/connection_settings/protocol_disable.rs b/src/commands/connection_settings/protocol_disable.rs index e216ce9d..386e5247 100644 --- a/src/commands/connection_settings/protocol_disable.rs +++ b/src/commands/connection_settings/protocol_disable.rs @@ -10,6 +10,8 @@ pub struct ProtocolDisableRequest(Vec); impl CommandRequest for ProtocolDisableRequest { const COMMAND: &'static str = "protocol disable"; + const MIN_ARGS: u32 = 1; + const MAX_ARGS: Option = None; fn into_request_enum(self) -> crate::Request { crate::Request::ProtocolDisable(self.0) @@ -35,16 +37,20 @@ impl CommandRequest for ProtocolDisableRequest { fn parse(parts: RequestTokenizer<'_>) -> Result { let mut parts = parts.peekable(); if parts.peek().is_none() { - return Err(RequestParserError::UnexpectedEOF); + return Err(Self::missing_arguments_error(0)); } - let mut features = Vec::with_capacity(parts.size_hint().0); - for part in parts { - let feature = part - .parse() - .map_err(|_| RequestParserError::SyntaxError(1, part.to_owned()))?; - features.push(feature); - } + let features = parts + .enumerate() + .map(|(i, f)| { + f.parse() + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: i.try_into().unwrap_or(u32::MAX), + expected_type: "Feature".to_owned(), + raw_input: f.to_owned(), + }) + }) + .collect::, RequestParserError>>()?; Ok(ProtocolDisableRequest(features)) } diff --git a/src/commands/connection_settings/protocol_enable.rs b/src/commands/connection_settings/protocol_enable.rs index 105d500c..23e595d6 100644 --- a/src/commands/connection_settings/protocol_enable.rs +++ b/src/commands/connection_settings/protocol_enable.rs @@ -10,6 +10,8 @@ pub struct ProtocolEnableRequest(Vec); impl CommandRequest for ProtocolEnableRequest { const COMMAND: &'static str = "protocol enable"; + const MIN_ARGS: u32 = 1; + const MAX_ARGS: Option = None; fn into_request_enum(self) -> crate::Request { crate::Request::ProtocolEnable(self.0) @@ -35,16 +37,20 @@ impl CommandRequest for ProtocolEnableRequest { fn parse(parts: RequestTokenizer<'_>) -> Result { let mut parts = parts.peekable(); if parts.peek().is_none() { - return Err(RequestParserError::UnexpectedEOF); + return Err(Self::missing_arguments_error(0)); } - let mut features = Vec::with_capacity(parts.size_hint().0); - for part in parts { - let feature = part - .parse() - .map_err(|_| RequestParserError::SyntaxError(1, part.to_owned()))?; - features.push(feature); - } + let features = parts + .enumerate() + .map(|(i, f)| { + f.parse() + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: i.try_into().unwrap_or(u32::MAX), + expected_type: "Feature".to_owned(), + raw_input: f.to_owned(), + }) + }) + .collect::, RequestParserError>>()?; Ok(ProtocolEnableRequest(features)) } diff --git a/src/commands/connection_settings/tag_types_disable.rs b/src/commands/connection_settings/tag_types_disable.rs index 31248b79..05bfdbb3 100644 --- a/src/commands/connection_settings/tag_types_disable.rs +++ b/src/commands/connection_settings/tag_types_disable.rs @@ -1,3 +1,5 @@ +use std::u32; + use crate::{ commands::{Command, CommandRequest, RequestParserError, empty_command_response}, request_tokenizer::RequestTokenizer, @@ -10,6 +12,8 @@ pub struct TagTypesDisableRequest(Vec); impl CommandRequest for TagTypesDisableRequest { const COMMAND: &'static str = "tagtypes disable"; + const MIN_ARGS: u32 = 1; + const MAX_ARGS: Option = None; fn into_request_enum(self) -> crate::Request { crate::Request::TagTypesDisable(self.0) @@ -37,13 +41,18 @@ impl CommandRequest for TagTypesDisableRequest { fn parse(parts: RequestTokenizer<'_>) -> Result { let mut parts = parts.peekable(); if parts.peek().is_none() { - return Err(RequestParserError::UnexpectedEOF); + return Err(Self::missing_arguments_error(0)); } let tag_types = parts - .map(|s| { + .enumerate() + .map(|(i, s)| { s.parse() - .map_err(|_| RequestParserError::SyntaxError(1, s.to_owned())) + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: i.try_into().unwrap_or(u32::MAX), + expected_type: "TagName".to_owned(), + raw_input: s.to_owned(), + }) }) .collect::, RequestParserError>>()?; diff --git a/src/commands/connection_settings/tag_types_enable.rs b/src/commands/connection_settings/tag_types_enable.rs index 714128ab..bfe14d1b 100644 --- a/src/commands/connection_settings/tag_types_enable.rs +++ b/src/commands/connection_settings/tag_types_enable.rs @@ -10,6 +10,8 @@ pub struct TagTypesEnableRequest(Vec); impl CommandRequest for TagTypesEnableRequest { const COMMAND: &'static str = "tagtypes enable"; + const MIN_ARGS: u32 = 1; + const MAX_ARGS: Option = None; fn into_request_enum(self) -> crate::Request { crate::Request::TagTypesEnable(self.0) @@ -37,13 +39,18 @@ impl CommandRequest for TagTypesEnableRequest { fn parse(parts: RequestTokenizer<'_>) -> Result { let mut parts = parts.peekable(); if parts.peek().is_none() { - return Err(RequestParserError::UnexpectedEOF); + return Err(Self::missing_arguments_error(0)); } let tag_types = parts - .map(|s| { + .enumerate() + .map(|(i, s)| { s.parse() - .map_err(|_| RequestParserError::SyntaxError(1, s.to_owned())) + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: i.try_into().unwrap_or(u32::MAX), + expected_type: "TagName".to_owned(), + raw_input: s.to_owned(), + }) }) .collect::, RequestParserError>>()?; diff --git a/src/commands/connection_settings/tag_types_reset.rs b/src/commands/connection_settings/tag_types_reset.rs index fe4b8a10..d745bc95 100644 --- a/src/commands/connection_settings/tag_types_reset.rs +++ b/src/commands/connection_settings/tag_types_reset.rs @@ -10,6 +10,8 @@ pub struct TagTypesResetRequest(Vec); impl CommandRequest for TagTypesResetRequest { const COMMAND: &'static str = "tagtypes reset"; + const MIN_ARGS: u32 = 1; + const MAX_ARGS: Option = None; fn into_request_enum(self) -> crate::Request { crate::Request::TagTypesReset(self.0) @@ -37,13 +39,18 @@ impl CommandRequest for TagTypesResetRequest { fn parse(parts: RequestTokenizer<'_>) -> Result { let mut parts = parts.peekable(); if parts.peek().is_none() { - return Err(RequestParserError::UnexpectedEOF); + return Err(Self::missing_arguments_error(0)); } let tag_types = parts - .map(|s| { + .enumerate() + .map(|(i, s)| { s.parse() - .map_err(|_| RequestParserError::SyntaxError(1, s.to_owned())) + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: i.try_into().unwrap_or(u32::MAX), + expected_type: "TagName".to_owned(), + raw_input: s.to_owned(), + }) }) .collect::, RequestParserError>>()?; diff --git a/src/commands/controlling_playback/pause.rs b/src/commands/controlling_playback/pause.rs index f4bea63f..43d741db 100644 --- a/src/commands/controlling_playback/pause.rs +++ b/src/commands/controlling_playback/pause.rs @@ -9,6 +9,8 @@ pub struct PauseRequest(Option); impl CommandRequest for PauseRequest { const COMMAND: &'static str = "pause"; + const MIN_ARGS: u32 = 0; + const MAX_ARGS: Option = Some(1); fn into_request_enum(self) -> crate::Request { crate::Request::Pause(self.0) @@ -33,11 +35,15 @@ impl CommandRequest for PauseRequest { let result = match parts.next() { Some("0") => Ok(Some(false)), Some("1") => Ok(Some(true)), - Some(s) => Err(RequestParserError::SyntaxError(0, s.to_string())), + Some(s) => Err(RequestParserError::SubtypeParserError { + argument_index: 0, + expected_type: "Option".to_owned(), + raw_input: s.to_owned(), + }), None => Ok(None), }; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; result.map(PauseRequest) } diff --git a/src/commands/controlling_playback/seek.rs b/src/commands/controlling_playback/seek.rs index 6db131f7..0213ef37 100644 --- a/src/commands/controlling_playback/seek.rs +++ b/src/commands/controlling_playback/seek.rs @@ -16,6 +16,8 @@ pub struct SeekRequest { impl CommandRequest for SeekRequest { const COMMAND: &'static str = "seek"; + const MIN_ARGS: u32 = 2; + const MAX_ARGS: Option = Some(2); fn into_request_enum(self) -> crate::Request { crate::Request::Seek(self.songpos, self.time) @@ -34,20 +36,30 @@ impl CommandRequest for SeekRequest { fn parse(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), + Some(s) => { + s.parse::() + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 0, + expected_type: "SongPosition".to_string(), + raw_input: s.to_owned(), + })? + } + + None => return Err(Self::missing_arguments_error(0)), }; let time = match parts.next() { - Some(t) => t - .parse::() - .map_err(|_| RequestParserError::SyntaxError(0, t.to_owned()))?, - None => return Err(RequestParserError::UnexpectedEOF), + Some(t) => t.parse::().map_err(|_| { + RequestParserError::SubtypeParserError { + argument_index: 1, + expected_type: "TimeWithFractions".to_string(), + raw_input: t.to_owned(), + } + })?, + None => return Err(Self::missing_arguments_error(1)), }; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(SeekRequest { songpos, time }) } diff --git a/src/commands/controlling_playback/seekcur.rs b/src/commands/controlling_playback/seekcur.rs index c77534be..d6ab3209 100644 --- a/src/commands/controlling_playback/seekcur.rs +++ b/src/commands/controlling_playback/seekcur.rs @@ -16,6 +16,8 @@ pub struct SeekCurRequest { impl CommandRequest for SeekCurRequest { const COMMAND: &'static str = "seekcur"; + const MIN_ARGS: u32 = 1; + const MAX_ARGS: Option = Some(1); fn into_request_enum(self) -> crate::Request { crate::Request::SeekCur(self.mode, self.time) @@ -41,29 +43,42 @@ impl CommandRequest for SeekCurRequest { fn parse(mut parts: RequestTokenizer<'_>) -> Result { let time_raw = match parts.next() { Some(t) => t, - None => return Err(RequestParserError::UnexpectedEOF), + None => return Err(Self::missing_arguments_error(0)), }; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; // TODO: DRY let (mode, time) = match time_raw { t if t.starts_with('+') => ( SeekMode::Relative, - t[1..] - .parse::() - .map_err(|_| RequestParserError::SyntaxError(0, t.to_owned()))?, + t[1..].parse::().map_err(|_| { + RequestParserError::SubtypeParserError { + argument_index: 0, + expected_type: "TimeWithFractions".to_string(), + raw_input: t[1..].to_owned(), + } + })?, ), t if t.starts_with('-') => ( SeekMode::RelativeReverse, - t[1..] - .parse::() - .map_err(|_| RequestParserError::SyntaxError(0, t.to_owned()))?, + t[1..].parse::().map_err(|_| { + RequestParserError::SubtypeParserError { + argument_index: 0, + expected_type: "TimeWithFractions".to_string(), + raw_input: t[1..].to_owned(), + } + })?, ), t => ( SeekMode::Absolute, - t.parse::() - .map_err(|_| RequestParserError::SyntaxError(0, t.to_owned()))?, + t.parse::().map_err(|_| { + RequestParserError::SubtypeParserError { + argument_index: 0, + expected_type: "TimeWithFractions".to_string(), + raw_input: t.to_owned(), + } + })?, ), }; diff --git a/src/commands/controlling_playback/seekid.rs b/src/commands/controlling_playback/seekid.rs index a3bc29d5..3f9eeb95 100644 --- a/src/commands/controlling_playback/seekid.rs +++ b/src/commands/controlling_playback/seekid.rs @@ -16,6 +16,8 @@ pub struct SeekIdRequest { impl CommandRequest for SeekIdRequest { const COMMAND: &'static str = "seekid"; + const MIN_ARGS: u32 = 2; + const MAX_ARGS: Option = Some(2); fn into_request_enum(self) -> crate::Request { crate::Request::SeekId(self.songid, self.time) @@ -36,18 +38,26 @@ impl CommandRequest for SeekIdRequest { let songid = match parts.next() { Some(s) => s .parse::() - .map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?, - None => return Err(RequestParserError::UnexpectedEOF), + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 0, + expected_type: "SongId".to_string(), + raw_input: s.to_owned(), + })?, + None => return Err(Self::missing_arguments_error(0)), }; let time = match parts.next() { - Some(t) => t - .parse::() - .map_err(|_| RequestParserError::SyntaxError(0, t.to_owned()))?, - None => return Err(RequestParserError::UnexpectedEOF), + Some(t) => t.parse::().map_err(|_| { + RequestParserError::SubtypeParserError { + argument_index: 1, + expected_type: "TimeWithFractions".to_string(), + raw_input: t.to_owned(), + } + })?, + None => return Err(Self::missing_arguments_error(1)), }; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(SeekIdRequest { songid, time }) } diff --git a/src/commands/mounts_and_neighbors/mount.rs b/src/commands/mounts_and_neighbors/mount.rs index 161d8dde..da5fe50d 100644 --- a/src/commands/mounts_and_neighbors/mount.rs +++ b/src/commands/mounts_and_neighbors/mount.rs @@ -16,6 +16,8 @@ pub struct MountRequest { impl CommandRequest for MountRequest { const COMMAND: &'static str = "mount"; + const MIN_ARGS: u32 = 2; + const MAX_ARGS: Option = Some(2); fn into_request_enum(self) -> crate::Request { crate::Request::Mount(self.path, self.uri) @@ -39,17 +41,21 @@ impl CommandRequest for MountRequest { } fn parse(mut parts: RequestTokenizer<'_>) -> Result { - let path = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let path = parts.next().ok_or(Self::missing_arguments_error(0))?; let path = path .parse() - .map_err(|_| RequestParserError::SyntaxError(0, path.to_string()))?; + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 0, + expected_type: "MountPath".to_string(), + raw_input: path.to_string(), + })?; let uri = parts .next() - .ok_or(RequestParserError::UnexpectedEOF)? + .ok_or(Self::missing_arguments_error(1))? .to_string(); - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(MountRequest { path, uri }) } diff --git a/src/commands/mounts_and_neighbors/unmount.rs b/src/commands/mounts_and_neighbors/unmount.rs index 3837e39d..c307cc4e 100644 --- a/src/commands/mounts_and_neighbors/unmount.rs +++ b/src/commands/mounts_and_neighbors/unmount.rs @@ -12,6 +12,8 @@ pub struct UnmountRequest(MountPath); impl CommandRequest for UnmountRequest { const COMMAND: &'static str = "unmount"; + const MIN_ARGS: u32 = 1; + const MAX_ARGS: Option = Some(1); fn into_request_enum(self) -> crate::Request { crate::Request::Unmount(self.0) @@ -34,14 +36,18 @@ impl CommandRequest for UnmountRequest { } 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()))?; + let path = parts.next().ok_or(Self::missing_arguments_error(0))?; + let path = + path.parse::() + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 0, + expected_type: "MountPath".to_string(), + raw_input: path.to_string(), + })?; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; - Ok(UnmountRequest(item)) + Ok(UnmountRequest(path)) } } diff --git a/src/commands/music_database/albumart.rs b/src/commands/music_database/albumart.rs index c3952d3c..a40e218d 100644 --- a/src/commands/music_database/albumart.rs +++ b/src/commands/music_database/albumart.rs @@ -19,6 +19,8 @@ pub struct AlbumArtRequest { impl CommandRequest for AlbumArtRequest { const COMMAND: &'static str = "albumart"; + const MIN_ARGS: u32 = 2; + const MAX_ARGS: Option = Some(2); fn into_request_enum(self) -> crate::Request { crate::Request::AlbumArt(self.uri, self.offset) @@ -38,17 +40,21 @@ impl CommandRequest for AlbumArtRequest { fn parse(mut parts: RequestTokenizer<'_>) -> Result { let uri = match parts.next() { Some(s) => s, - None => return Err(RequestParserError::UnexpectedEOF), + None => return Err(Self::missing_arguments_error(0)), }; let offset = match parts.next() { Some(s) => s .parse::() - .map_err(|_| RequestParserError::SyntaxError(1, s.to_owned()))?, - None => return Err(RequestParserError::UnexpectedEOF), + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 1, + expected_type: "Offset".to_string(), + raw_input: s.to_string(), + })?, + None => return Err(Self::missing_arguments_error(1)), }; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(AlbumArtRequest { uri: uri.to_string(), diff --git a/src/commands/music_database/count.rs b/src/commands/music_database/count.rs index c72330de..a4dada76 100644 --- a/src/commands/music_database/count.rs +++ b/src/commands/music_database/count.rs @@ -20,6 +20,8 @@ pub struct CountRequest { impl CommandRequest for CountRequest { const COMMAND: &'static str = "count"; + const MIN_ARGS: u32 = 1; + const MAX_ARGS: Option = Some(2); fn into_request_enum(self) -> crate::Request { crate::Request::Count(self.filter, self.group) @@ -46,22 +48,26 @@ impl CommandRequest for CountRequest { Some(f) => { Filter::parse(f).map_err(|_| RequestParserError::SyntaxError(1, f.to_owned()))? } - None => return Err(RequestParserError::UnexpectedEOF), + None => return Err(Self::missing_arguments_error(0)), }; let group = if let Some("group") = parts.next() { - let group = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let group = parts.next().ok_or(Self::missing_arguments_error(1))?; Some( group .parse() - .map_err(|_| RequestParserError::SyntaxError(1, group.to_owned()))?, + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 1, + expected_type: "GroupType".to_string(), + raw_input: group.to_owned(), + })?, ) } else { None }; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(CountRequest { filter, group }) } diff --git a/src/commands/music_database/find.rs b/src/commands/music_database/find.rs index 6cb3d8ef..a6365ce8 100644 --- a/src/commands/music_database/find.rs +++ b/src/commands/music_database/find.rs @@ -19,6 +19,8 @@ pub struct FindRequest { impl CommandRequest for FindRequest { const COMMAND: &'static str = "find"; + const MIN_ARGS: u32 = 1; + const MAX_ARGS: Option = Some(3); fn into_request_enum(self) -> crate::Request { crate::Request::Find(self.filter, self.sort, self.window) @@ -52,13 +54,13 @@ impl CommandRequest for FindRequest { Some(f) => { Filter::parse(f).map_err(|_| RequestParserError::SyntaxError(1, f.to_owned()))? } - None => return Err(RequestParserError::UnexpectedEOF), + None => return Err(Self::missing_arguments_error(0)), }; let mut sort_or_window = parts.next(); let mut sort = None; if let Some("sort") = sort_or_window { - let s = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let s = parts.next().ok_or(Self::missing_arguments_error(1))?; sort = Some( s.parse() .map_err(|_| RequestParserError::SyntaxError(0, s.to_string()))?, @@ -68,14 +70,14 @@ impl CommandRequest for FindRequest { let mut window = None; if let Some("window") = sort_or_window { - let w = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let w = parts.next().ok_or(Self::missing_arguments_error(2))?; window = Some( w.parse() .map_err(|_| RequestParserError::SyntaxError(0, w.to_string()))?, ); } - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(FindRequest { filter, diff --git a/src/commands/music_database/findadd.rs b/src/commands/music_database/findadd.rs index 5d5f3b7f..93763bf4 100644 --- a/src/commands/music_database/findadd.rs +++ b/src/commands/music_database/findadd.rs @@ -19,6 +19,8 @@ pub struct FindAddRequest { impl CommandRequest for FindAddRequest { const COMMAND: &'static str = "findadd"; + const MIN_ARGS: u32 = 1; + const MAX_ARGS: Option = Some(4); fn into_request_enum(self) -> crate::Request { crate::Request::FindAdd(self.filter, self.sort, self.window, self.position) @@ -56,40 +58,52 @@ impl CommandRequest for FindAddRequest { Some(f) => { Filter::parse(f).map_err(|_| RequestParserError::SyntaxError(1, f.to_owned()))? } - None => return Err(RequestParserError::UnexpectedEOF), + None => return Err(Self::missing_arguments_error(0)), }; let mut sort_or_window_or_position = parts.next(); let mut sort = None; if let Some("sort") = sort_or_window_or_position { - let s = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let s = parts.next().ok_or(Self::missing_arguments_error(1))?; sort = Some( s.parse() - .map_err(|_| RequestParserError::SyntaxError(0, s.to_string()))?, + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 1, + expected_type: "Sort".to_string(), + raw_input: s.to_owned(), + })?, ); sort_or_window_or_position = parts.next(); } let mut window = None; if let Some("window") = sort_or_window_or_position { - let w = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let w = parts.next().ok_or(Self::missing_arguments_error(2))?; window = Some( w.parse() - .map_err(|_| RequestParserError::SyntaxError(0, w.to_string()))?, + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 2, + expected_type: "WindowRange".to_string(), + raw_input: w.to_owned(), + })?, ); sort_or_window_or_position = parts.next(); } let mut position = None; if let Some("position") = sort_or_window_or_position { - let p = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let p = parts.next().ok_or(Self::missing_arguments_error(3))?; position = Some( p.parse() - .map_err(|_| RequestParserError::SyntaxError(0, p.to_string()))?, + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 3, + expected_type: "SongPosition".to_string(), + raw_input: p.to_owned(), + })?, ); } - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(FindAddRequest { filter, diff --git a/src/commands/music_database/list.rs b/src/commands/music_database/list.rs index 8ddafbc6..19d8edd8 100644 --- a/src/commands/music_database/list.rs +++ b/src/commands/music_database/list.rs @@ -20,6 +20,8 @@ pub struct ListRequest { impl CommandRequest for ListRequest { const COMMAND: &'static str = "list"; + const MIN_ARGS: u32 = 1; + const MAX_ARGS: Option = None; fn into_request_enum(self) -> crate::Request { crate::Request::List(self.tagname, self.filter, self.groups, self.window) @@ -53,10 +55,14 @@ impl CommandRequest for ListRequest { } fn parse(mut parts: RequestTokenizer<'_>) -> Result { - let tagname = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let tagname = parts.next().ok_or(Self::missing_arguments_error(0))?; let tagname = tagname .parse() - .map_err(|_| RequestParserError::SyntaxError(1, tagname.to_owned()))?; + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 0, + expected_type: "TagName".to_string(), + raw_input: tagname.to_owned(), + })?; let mut filter = None; let mut groups = Vec::new(); @@ -77,10 +83,15 @@ impl CommandRequest for ListRequest { while let Some(g) = next && g == "group" { - let group = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; - let parsed_group = group - .parse() - .map_err(|_| RequestParserError::SyntaxError(1, group.to_owned()))?; + let group = parts.next().ok_or(Self::missing_arguments_error(2))?; + let parsed_group = + group + .parse() + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 2, + expected_type: "GroupType".to_string(), + raw_input: group.to_owned(), + })?; groups.push(parsed_group); next = parts.next(); } @@ -88,14 +99,19 @@ impl CommandRequest for ListRequest { if let Some(w) = next && w == "window" { - let window_str = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; - let parsed_window = window_str - .parse() - .map_err(|_| RequestParserError::SyntaxError(1, window_str.to_owned()))?; + let window_str = parts.next().ok_or(Self::missing_arguments_error(3))?; + let parsed_window = + window_str + .parse() + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 3, + expected_type: "WindowRange".to_string(), + raw_input: window_str.to_owned(), + })?; window = Some(parsed_window); } - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(ListRequest { tagname, diff --git a/src/commands/music_database/readpicture.rs b/src/commands/music_database/readpicture.rs index bc15cbc7..30a15cee 100644 --- a/src/commands/music_database/readpicture.rs +++ b/src/commands/music_database/readpicture.rs @@ -21,6 +21,8 @@ pub struct ReadPictureRequest { impl CommandRequest for ReadPictureRequest { const COMMAND: &'static str = "readpicture"; + const MIN_ARGS: u32 = 2; + const MAX_ARGS: Option = Some(2); fn into_request_enum(self) -> crate::Request { crate::Request::ReadPicture(self.uri, self.offset) @@ -40,17 +42,21 @@ impl CommandRequest for ReadPictureRequest { fn parse(mut parts: RequestTokenizer<'_>) -> Result { let uri = match parts.next() { Some(s) => s, - None => return Err(RequestParserError::UnexpectedEOF), + None => return Err(Self::missing_arguments_error(0)), }; let offset = match parts.next() { Some(s) => s .parse::() - .map_err(|_| RequestParserError::SyntaxError(1, s.to_owned()))?, - None => return Err(RequestParserError::UnexpectedEOF), + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 1, + expected_type: "Offset".to_string(), + raw_input: s.to_owned(), + })?, + None => return Err(Self::missing_arguments_error(1)), }; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(ReadPictureRequest { uri: uri.to_string(), diff --git a/src/commands/music_database/search.rs b/src/commands/music_database/search.rs index d88bc573..3f643e2e 100644 --- a/src/commands/music_database/search.rs +++ b/src/commands/music_database/search.rs @@ -19,6 +19,8 @@ pub struct SearchRequest { impl CommandRequest for SearchRequest { const COMMAND: &'static str = "search"; + const MIN_ARGS: u32 = 1; + const MAX_ARGS: Option = Some(3); fn into_request_enum(self) -> crate::Request { crate::Request::Search(self.filter, self.sort, self.window) @@ -52,13 +54,13 @@ impl CommandRequest for SearchRequest { Some(f) => { Filter::parse(f).map_err(|_| RequestParserError::SyntaxError(1, f.to_owned()))? } - None => return Err(RequestParserError::UnexpectedEOF), + None => return Err(Self::missing_arguments_error(0)), }; let mut sort_or_window = parts.next(); let mut sort = None; if let Some("sort") = sort_or_window { - let s = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let s = parts.next().ok_or(Self::missing_arguments_error(1))?; sort = Some( s.parse() .map_err(|_| RequestParserError::SyntaxError(0, s.to_string()))?, @@ -75,7 +77,7 @@ impl CommandRequest for SearchRequest { ); } - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(SearchRequest { filter, diff --git a/src/commands/music_database/searchadd.rs b/src/commands/music_database/searchadd.rs index 4f09b36b..cbf6a7f4 100644 --- a/src/commands/music_database/searchadd.rs +++ b/src/commands/music_database/searchadd.rs @@ -19,6 +19,8 @@ pub struct SearchAddRequest { impl CommandRequest for SearchAddRequest { const COMMAND: &'static str = "searchadd"; + const MIN_ARGS: u32 = 1; + const MAX_ARGS: Option = Some(4); fn into_request_enum(self) -> crate::Request { crate::Request::SearchAdd(self.filter, self.sort, self.window, self.position) @@ -56,7 +58,7 @@ impl CommandRequest for SearchAddRequest { Some(f) => { Filter::parse(f).map_err(|_| RequestParserError::SyntaxError(1, f.to_owned()))? } - None => return Err(RequestParserError::UnexpectedEOF), + None => return Err(Self::missing_arguments_error(0)), }; let mut sort_or_window_or_position = parts.next(); @@ -89,7 +91,7 @@ impl CommandRequest for SearchAddRequest { ); } - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(SearchAddRequest { filter, diff --git a/src/commands/music_database/searchaddpl.rs b/src/commands/music_database/searchaddpl.rs index 3a77287c..b4556f62 100644 --- a/src/commands/music_database/searchaddpl.rs +++ b/src/commands/music_database/searchaddpl.rs @@ -20,6 +20,8 @@ pub struct SearchAddPlRequest { impl CommandRequest for SearchAddPlRequest { const COMMAND: &'static str = "searchaddpl"; + const MIN_ARGS: u32 = 2; + const MAX_ARGS: Option = Some(5); fn into_request_enum(self) -> crate::Request { crate::Request::SearchAddPl( @@ -104,7 +106,7 @@ impl CommandRequest for SearchAddPlRequest { ); } - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(SearchAddPlRequest { playlist_name, diff --git a/src/commands/music_database/searchcount.rs b/src/commands/music_database/searchcount.rs index 0cb357f4..ab176d17 100644 --- a/src/commands/music_database/searchcount.rs +++ b/src/commands/music_database/searchcount.rs @@ -20,6 +20,8 @@ pub struct SearchCountRequest { impl CommandRequest for SearchCountRequest { const COMMAND: &'static str = "searchcount"; + const MIN_ARGS: u32 = 1; + const MAX_ARGS: Option = Some(2); fn into_request_enum(self) -> crate::Request { crate::Request::SearchCount(self.filter, self.group) @@ -48,7 +50,7 @@ impl CommandRequest for SearchCountRequest { Some(f) => { Filter::parse(f).map_err(|_| RequestParserError::SyntaxError(1, f.to_owned()))? } - None => return Err(RequestParserError::UnexpectedEOF), + None => return Err(Self::missing_arguments_error(0)), }; let group = if let Some("group") = parts.next() { @@ -62,7 +64,7 @@ impl CommandRequest for SearchCountRequest { None }; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(SearchCountRequest { filter, group }) } diff --git a/src/commands/playback_options/random.rs b/src/commands/playback_options/random.rs index 77bee431..9422d597 100644 --- a/src/commands/playback_options/random.rs +++ b/src/commands/playback_options/random.rs @@ -9,6 +9,8 @@ pub struct RandomRequest(bool); impl CommandRequest for RandomRequest { const COMMAND: &'static str = "random"; + const MIN_ARGS: u32 = 1; + const MAX_ARGS: Option = Some(1); fn into_request_enum(self) -> crate::Request { crate::Request::Random(self.0) @@ -30,11 +32,17 @@ impl CommandRequest for RandomRequest { let state = match parts.next() { Some("0") => false, Some("1") => true, - Some(s) => return Err(RequestParserError::SyntaxError(0, s.to_owned())), - None => return Err(RequestParserError::UnexpectedEOF), + Some(s) => { + return Err(RequestParserError::SubtypeParserError { + argument_index: 0, + expected_type: "bool".to_owned(), + raw_input: s.to_owned(), + }); + } + None => return Err(Self::missing_arguments_error(0)), }; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(RandomRequest(state)) } diff --git a/src/commands/playback_options/repeat.rs b/src/commands/playback_options/repeat.rs index 2e3ad3dc..05ea197d 100644 --- a/src/commands/playback_options/repeat.rs +++ b/src/commands/playback_options/repeat.rs @@ -9,6 +9,8 @@ pub struct RepeatRequest(bool); impl CommandRequest for RepeatRequest { const COMMAND: &'static str = "repeat"; + const MIN_ARGS: u32 = 1; + const MAX_ARGS: Option = Some(1); fn into_request_enum(self) -> crate::Request { crate::Request::Repeat(self.0) @@ -30,11 +32,17 @@ impl CommandRequest for RepeatRequest { let state = match parts.next() { Some("0") => false, Some("1") => true, - Some(s) => return Err(RequestParserError::SyntaxError(0, s.to_owned())), - None => return Err(RequestParserError::UnexpectedEOF), + Some(s) => { + return Err(RequestParserError::SubtypeParserError { + argument_index: 0, + expected_type: "bool".to_owned(), + raw_input: s.to_owned(), + }); + } + None => return Err(Self::missing_arguments_error(0)), }; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(RepeatRequest(state)) } diff --git a/src/commands/querying_mpd_status/idle.rs b/src/commands/querying_mpd_status/idle.rs index 372ca983..b9828487 100644 --- a/src/commands/querying_mpd_status/idle.rs +++ b/src/commands/querying_mpd_status/idle.rs @@ -12,6 +12,8 @@ pub struct IdleRequest(Option>); impl CommandRequest for IdleRequest { const COMMAND: &'static str = "idle"; + const MIN_ARGS: u32 = 0; + const MAX_ARGS: Option = None; fn into_request_enum(self) -> crate::Request { crate::Request::Idle(self.0) @@ -48,8 +50,6 @@ impl CommandRequest for IdleRequest { Ok(Some(subsystems)) }); - debug_assert!(parts.next().is_none()); - result.map(IdleRequest) } } diff --git a/src/commands/queue/add.rs b/src/commands/queue/add.rs index 85733c93..594e4a97 100644 --- a/src/commands/queue/add.rs +++ b/src/commands/queue/add.rs @@ -16,6 +16,8 @@ pub struct AddRequest { impl CommandRequest for AddRequest { const COMMAND: &'static str = "add"; + const MIN_ARGS: u32 = 1; + const MAX_ARGS: Option = Some(2); fn into_request_enum(self) -> crate::Request { crate::Request::Add(self.uri, self.position) @@ -38,18 +40,21 @@ impl CommandRequest for AddRequest { fn parse(mut parts: RequestTokenizer<'_>) -> Result { let uri = match parts.next() { Some(s) => s, - None => return Err(RequestParserError::UnexpectedEOF), + None => return Err(Self::missing_arguments_error(0)), }; let position = match parts.next() { - Some(s) => Some( - s.parse::() - .map_err(|_| RequestParserError::SyntaxError(1, s.to_owned()))?, - ), + Some(s) => Some(s.parse::().map_err(|_| { + RequestParserError::SubtypeParserError { + argument_index: 1, + expected_type: "SongPosition".to_string(), + raw_input: s.to_owned(), + } + })?), None => None, }; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(AddRequest { uri: uri.to_string(), diff --git a/src/commands/queue/addid.rs b/src/commands/queue/addid.rs index 7a0668db..9c0c1dad 100644 --- a/src/commands/queue/addid.rs +++ b/src/commands/queue/addid.rs @@ -17,6 +17,8 @@ pub struct AddIdRequest { impl CommandRequest for AddIdRequest { const COMMAND: &'static str = "addid"; + const MIN_ARGS: u32 = 1; + const MAX_ARGS: Option = Some(2); fn into_request_enum(self) -> crate::Request { crate::Request::AddId(self.uri, self.position) @@ -39,18 +41,21 @@ impl CommandRequest for AddIdRequest { fn parse(mut parts: RequestTokenizer<'_>) -> Result { let uri = match parts.next() { Some(s) => s, - None => return Err(RequestParserError::UnexpectedEOF), + None => return Err(Self::missing_arguments_error(0)), }; let position = match parts.next() { - Some(s) => Some( - s.parse::() - .map_err(|_| RequestParserError::SyntaxError(1, s.to_owned()))?, - ), + Some(s) => Some(s.parse::().map_err(|_| { + RequestParserError::SubtypeParserError { + argument_index: 1, + expected_type: "SongPosition".to_string(), + raw_input: s.to_owned(), + } + })?), None => None, }; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(AddIdRequest { uri: uri.to_string(), diff --git a/src/commands/queue/addtagid.rs b/src/commands/queue/addtagid.rs index 32d88688..79abc4f2 100644 --- a/src/commands/queue/addtagid.rs +++ b/src/commands/queue/addtagid.rs @@ -17,6 +17,8 @@ pub struct AddTagIdRequest { impl CommandRequest for AddTagIdRequest { const COMMAND: &'static str = "addtagid"; + const MIN_ARGS: u32 = 3; + const MAX_ARGS: Option = Some(3); fn into_request_enum(self) -> crate::Request { crate::Request::AddTagId(self.songid, self.tag_name, self.tag_value) @@ -44,22 +46,34 @@ impl CommandRequest for AddTagIdRequest { } fn parse(mut parts: RequestTokenizer<'_>) -> Result { - let songid = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let songid = parts.next().ok_or(Self::missing_arguments_error(0))?; let songid = songid .parse() - .map_err(|_| RequestParserError::SyntaxError(0, songid.to_string()))?; + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 0, + expected_type: "SongId".to_string(), + raw_input: songid.to_owned(), + })?; - let tag_name = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let tag_name = parts.next().ok_or(Self::missing_arguments_error(1))?; let tag_name = tag_name .parse() - .map_err(|_| RequestParserError::SyntaxError(0, tag_name.to_string()))?; + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 1, + expected_type: "TagName".to_string(), + raw_input: tag_name.to_owned(), + })?; - let tag_value = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let tag_value = parts.next().ok_or(Self::missing_arguments_error(2))?; let tag_value = tag_value .parse() - .map_err(|_| RequestParserError::SyntaxError(0, tag_value.to_string()))?; + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 2, + expected_type: "TagValue".to_string(), + raw_input: tag_value.to_owned(), + })?; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(AddTagIdRequest { songid, diff --git a/src/commands/queue/cleartagid.rs b/src/commands/queue/cleartagid.rs index 30ac1731..1cf9c6b7 100644 --- a/src/commands/queue/cleartagid.rs +++ b/src/commands/queue/cleartagid.rs @@ -16,6 +16,8 @@ pub struct ClearTagIdRequest { impl CommandRequest for ClearTagIdRequest { const COMMAND: &'static str = "cleartagid"; + const MIN_ARGS: u32 = 2; + const MAX_ARGS: Option = Some(2); fn into_request_enum(self) -> crate::Request { crate::Request::ClearTagId(self.songid, self.tag_name) @@ -35,17 +37,25 @@ impl CommandRequest for ClearTagIdRequest { } fn parse(mut parts: RequestTokenizer<'_>) -> Result { - let songid = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let songid = parts.next().ok_or(Self::missing_arguments_error(0))?; let songid = songid .parse() - .map_err(|_| RequestParserError::SyntaxError(0, songid.to_string()))?; + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 0, + expected_type: "SongId".to_string(), + raw_input: songid.to_string(), + })?; - let tag_name = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let tag_name = parts.next().ok_or(Self::missing_arguments_error(1))?; let tag_name = tag_name .parse() - .map_err(|_| RequestParserError::SyntaxError(0, tag_name.to_string()))?; + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 1, + expected_type: "TagName".to_string(), + raw_input: tag_name.to_string(), + })?; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(ClearTagIdRequest { songid, tag_name }) } diff --git a/src/commands/queue/move_.rs b/src/commands/queue/move_.rs index 62e595f8..e3ae52e4 100644 --- a/src/commands/queue/move_.rs +++ b/src/commands/queue/move_.rs @@ -16,6 +16,8 @@ pub struct MoveRequest { impl CommandRequest for MoveRequest { const COMMAND: &'static str = "move"; + const MIN_ARGS: u32 = 2; + const MAX_ARGS: Option = Some(2); fn into_request_enum(self) -> crate::Request { crate::Request::Move(self.from_or_range, self.to) @@ -43,7 +45,7 @@ impl CommandRequest for MoveRequest { .parse() .map_err(|_| RequestParserError::SyntaxError(0, to.to_string()))?; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(MoveRequest { from_or_range, to }) } diff --git a/src/commands/queue/moveid.rs b/src/commands/queue/moveid.rs index 9148fbc8..382f89d7 100644 --- a/src/commands/queue/moveid.rs +++ b/src/commands/queue/moveid.rs @@ -16,6 +16,8 @@ pub struct MoveIdRequest { impl CommandRequest for MoveIdRequest { const COMMAND: &'static str = "moveid"; + const MIN_ARGS: u32 = 2; + const MAX_ARGS: Option = Some(2); fn into_request_enum(self) -> crate::Request { crate::Request::MoveId(self.id, self.to) @@ -43,7 +45,7 @@ impl CommandRequest for MoveIdRequest { .parse() .map_err(|_| RequestParserError::SyntaxError(0, to.to_string()))?; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(MoveIdRequest { id, to }) } diff --git a/src/commands/queue/playlistfind.rs b/src/commands/queue/playlistfind.rs index 9a14d6e9..fb1f94a1 100644 --- a/src/commands/queue/playlistfind.rs +++ b/src/commands/queue/playlistfind.rs @@ -20,6 +20,8 @@ pub struct PlaylistFindRequest { impl CommandRequest for PlaylistFindRequest { const COMMAND: &'static str = "playlistfind"; + const MIN_ARGS: u32 = 1; + const MAX_ARGS: Option = Some(3); fn into_request_enum(self) -> crate::Request { crate::Request::PlaylistFind(self.filter, self.sort, self.window) @@ -53,7 +55,7 @@ impl CommandRequest for PlaylistFindRequest { Some(f) => { Filter::parse(f).map_err(|_| RequestParserError::SyntaxError(1, f.to_owned()))? } - None => return Err(RequestParserError::UnexpectedEOF), + None => return Err(Self::missing_arguments_error(0)), }; let mut sort_or_window = parts.next(); @@ -76,7 +78,7 @@ impl CommandRequest for PlaylistFindRequest { ); } - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(PlaylistFindRequest { filter, diff --git a/src/commands/queue/playlistsearch.rs b/src/commands/queue/playlistsearch.rs index 5b8d8504..10ce3df2 100644 --- a/src/commands/queue/playlistsearch.rs +++ b/src/commands/queue/playlistsearch.rs @@ -19,6 +19,8 @@ pub struct PlaylistSearchRequest { impl CommandRequest for PlaylistSearchRequest { const COMMAND: &'static str = "playlistsearch"; + const MIN_ARGS: u32 = 1; + const MAX_ARGS: Option = Some(3); fn into_request_enum(self) -> crate::Request { crate::Request::PlaylistSearch(self.filter, self.sort, self.window) @@ -52,7 +54,7 @@ impl CommandRequest for PlaylistSearchRequest { Some(f) => { Filter::parse(f).map_err(|_| RequestParserError::SyntaxError(1, f.to_owned()))? } - None => return Err(RequestParserError::UnexpectedEOF), + None => return Err(Self::missing_arguments_error(0)), }; let mut sort_or_window = parts.next(); @@ -75,7 +77,7 @@ impl CommandRequest for PlaylistSearchRequest { ); } - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(PlaylistSearchRequest { filter, diff --git a/src/commands/queue/plchanges.rs b/src/commands/queue/plchanges.rs index 0bb74fdf..2d3788a9 100644 --- a/src/commands/queue/plchanges.rs +++ b/src/commands/queue/plchanges.rs @@ -17,6 +17,8 @@ pub struct PlChangesRequest { impl CommandRequest for PlChangesRequest { const COMMAND: &'static str = "plchanges"; + const MIN_ARGS: u32 = 1; + const MAX_ARGS: Option = Some(2); fn into_request_enum(self) -> crate::Request { crate::Request::PlChanges(self.version, self.window) @@ -52,7 +54,7 @@ impl CommandRequest for PlChangesRequest { }) .transpose()?; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(PlChangesRequest { version, window }) } diff --git a/src/commands/queue/plchangesposid.rs b/src/commands/queue/plchangesposid.rs index 12a1daad..fce82e86 100644 --- a/src/commands/queue/plchangesposid.rs +++ b/src/commands/queue/plchangesposid.rs @@ -17,6 +17,8 @@ pub struct PlChangesPosIdRequest { impl CommandRequest for PlChangesPosIdRequest { const COMMAND: &'static str = "plchangesposid"; + const MIN_ARGS: u32 = 1; + const MAX_ARGS: Option = Some(2); fn into_request_enum(self) -> crate::Request { crate::Request::PlChangesPosId(self.version, self.window) @@ -52,7 +54,7 @@ impl CommandRequest for PlChangesPosIdRequest { }) .transpose()?; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(PlChangesPosIdRequest { version, window }) } diff --git a/src/commands/queue/prio.rs b/src/commands/queue/prio.rs index b46bb580..4b7cec64 100644 --- a/src/commands/queue/prio.rs +++ b/src/commands/queue/prio.rs @@ -16,6 +16,8 @@ pub struct PrioRequest { impl CommandRequest for PrioRequest { const COMMAND: &'static str = "prio"; + const MIN_ARGS: u32 = 2; + const MAX_ARGS: Option = Some(2); fn into_request_enum(self) -> crate::Request { crate::Request::Prio(self.prio, self.window) @@ -43,7 +45,7 @@ impl CommandRequest for PrioRequest { .parse() .map_err(|_| RequestParserError::SyntaxError(0, window.to_string()))?; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(PrioRequest { prio, window }) } diff --git a/src/commands/queue/prioid.rs b/src/commands/queue/prioid.rs index 2312eba2..7930fc04 100644 --- a/src/commands/queue/prioid.rs +++ b/src/commands/queue/prioid.rs @@ -16,6 +16,9 @@ pub struct PrioIdRequest { impl CommandRequest for PrioIdRequest { const COMMAND: &'static str = "prioid"; + const MIN_ARGS: u32 = 2; + // TODO: should this be 2? + const MAX_ARGS: Option = None; fn into_request_enum(self) -> crate::Request { crate::Request::PrioId(self.prio, self.songids) @@ -53,7 +56,7 @@ impl CommandRequest for PrioIdRequest { }) .collect::, RequestParserError>>()?; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(PrioIdRequest { prio, songids }) } diff --git a/src/commands/queue/rangeid.rs b/src/commands/queue/rangeid.rs index df1ccd2b..eaa0a465 100644 --- a/src/commands/queue/rangeid.rs +++ b/src/commands/queue/rangeid.rs @@ -16,6 +16,8 @@ pub struct RangeIdRequest { impl CommandRequest for RangeIdRequest { const COMMAND: &'static str = "rangeid"; + const MIN_ARGS: u32 = 2; + const MAX_ARGS: Option = Some(2); fn into_request_enum(self) -> crate::Request { crate::Request::RangeId(self.songid, self.time_interval) @@ -46,7 +48,7 @@ impl CommandRequest for RangeIdRequest { .parse() .map_err(|_| RequestParserError::SyntaxError(0, time_interval.to_string()))?; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(RangeIdRequest { songid, diff --git a/src/commands/queue/swap.rs b/src/commands/queue/swap.rs index 26d4719a..25357bbe 100644 --- a/src/commands/queue/swap.rs +++ b/src/commands/queue/swap.rs @@ -16,6 +16,8 @@ pub struct SwapRequest { impl CommandRequest for SwapRequest { const COMMAND: &'static str = "swap"; + const MIN_ARGS: u32 = 2; + const MAX_ARGS: Option = Some(2); fn into_request_enum(self) -> crate::Request { crate::Request::Swap(self.songpos1, self.songpos2) @@ -43,7 +45,7 @@ impl CommandRequest for SwapRequest { .parse() .map_err(|_| RequestParserError::SyntaxError(0, songpos2.to_string()))?; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(SwapRequest { songpos1, songpos2 }) } diff --git a/src/commands/queue/swapid.rs b/src/commands/queue/swapid.rs index 618cf2ac..c23e22a2 100644 --- a/src/commands/queue/swapid.rs +++ b/src/commands/queue/swapid.rs @@ -16,6 +16,8 @@ pub struct SwapIdRequest { impl CommandRequest for SwapIdRequest { const COMMAND: &'static str = "swapid"; + const MIN_ARGS: u32 = 2; + const MAX_ARGS: Option = Some(2); fn into_request_enum(self) -> crate::Request { crate::Request::SwapId(self.songid1, self.songid2) @@ -43,7 +45,7 @@ impl CommandRequest for SwapIdRequest { .parse() .map_err(|_| RequestParserError::SyntaxError(0, songid2.to_string()))?; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(SwapIdRequest { songid1, songid2 }) } diff --git a/src/commands/stickers/sticker_dec.rs b/src/commands/stickers/sticker_dec.rs index 5592ff1b..ea5b945c 100644 --- a/src/commands/stickers/sticker_dec.rs +++ b/src/commands/stickers/sticker_dec.rs @@ -18,6 +18,8 @@ pub struct StickerDecRequest { impl CommandRequest for StickerDecRequest { const COMMAND: &'static str = "sticker dec"; + const MIN_ARGS: u32 = 4; + const MAX_ARGS: Option = Some(4); fn into_request_enum(self) -> crate::Request { crate::Request::StickerDec(self.sticker_type, self.uri, self.name, self.value) @@ -47,27 +49,40 @@ impl CommandRequest for StickerDecRequest { } 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()))?; + let sticker_type = parts.next().ok_or(Self::missing_arguments_error(0))?; + let sticker_type = + sticker_type + .parse() + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 0, + expected_type: "StickerType".to_string(), + raw_input: sticker_type.to_string(), + })?; - let uri = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let uri = parts.next().ok_or(Self::missing_arguments_error(1))?; let uri = uri .parse() - .map_err(|_| RequestParserError::SyntaxError(1, uri.to_owned()))?; + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 1, + expected_type: "Uri".to_string(), + raw_input: uri.to_string(), + })?; - let name = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let name = parts.next().ok_or(Self::missing_arguments_error(2))?; let name = name .parse() - .map_err(|_| RequestParserError::SyntaxError(1, name.to_owned()))?; + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 2, + expected_type: "String".to_string(), + raw_input: name.to_string(), + })?; 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()); + Self::throw_if_too_many_arguments(parts)?; Ok(StickerDecRequest { sticker_type, diff --git a/src/commands/stickers/sticker_delete.rs b/src/commands/stickers/sticker_delete.rs index 3b5f0b2c..abef984e 100644 --- a/src/commands/stickers/sticker_delete.rs +++ b/src/commands/stickers/sticker_delete.rs @@ -17,6 +17,8 @@ pub struct StickerDeleteRequest { impl CommandRequest for StickerDeleteRequest { const COMMAND: &'static str = "sticker delete"; + const MIN_ARGS: u32 = 3; + const MAX_ARGS: Option = Some(3); fn into_request_enum(self) -> crate::Request { crate::Request::StickerDelete(self.sticker_type, self.uri, self.name) @@ -44,22 +46,35 @@ impl CommandRequest for StickerDeleteRequest { } 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()))?; + let sticker_type = parts.next().ok_or(Self::missing_arguments_error(0))?; + let sticker_type = + sticker_type + .parse() + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 0, + expected_type: "StickerType".to_string(), + raw_input: sticker_type.to_string(), + })?; - let uri = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let uri = parts.next().ok_or(Self::missing_arguments_error(1))?; let uri = uri .parse() - .map_err(|_| RequestParserError::SyntaxError(1, uri.to_owned()))?; + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 1, + expected_type: "Uri".to_string(), + raw_input: uri.to_string(), + })?; - let name = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let name = parts.next().ok_or(Self::missing_arguments_error(2))?; let name = name .parse() - .map_err(|_| RequestParserError::SyntaxError(1, name.to_owned()))?; + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 2, + expected_type: "String".to_string(), + raw_input: name.to_string(), + })?; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(StickerDeleteRequest { sticker_type, diff --git a/src/commands/stickers/sticker_find.rs b/src/commands/stickers/sticker_find.rs index 38bcc1aa..90fa2fe5 100644 --- a/src/commands/stickers/sticker_find.rs +++ b/src/commands/stickers/sticker_find.rs @@ -20,6 +20,8 @@ pub struct StickerFindRequest { impl CommandRequest for StickerFindRequest { const COMMAND: &'static str = "sticker find"; + const MIN_ARGS: u32 = 3; + const MAX_ARGS: Option = Some(5); fn into_request_enum(self) -> crate::Request { crate::Request::StickerFind( @@ -65,20 +67,33 @@ impl CommandRequest for StickerFindRequest { } 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()))?; + let sticker_type = parts.next().ok_or(Self::missing_arguments_error(0))?; + let sticker_type = + sticker_type + .parse() + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 0, + expected_type: "StickerType".to_string(), + raw_input: sticker_type.to_string(), + })?; - let uri = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let uri = parts.next().ok_or(Self::missing_arguments_error(1))?; let uri = uri .parse() - .map_err(|_| RequestParserError::SyntaxError(1, uri.to_owned()))?; + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 1, + expected_type: "Uri".to_string(), + raw_input: uri.to_string(), + })?; - let name = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let name = parts.next().ok_or(Self::missing_arguments_error(2))?; let name = name .parse() - .map_err(|_| RequestParserError::SyntaxError(1, name.to_owned()))?; + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 2, + expected_type: "String".to_string(), + raw_input: name.to_string(), + })?; let mut sort_or_window = parts.next(); let mut sort = None; @@ -100,7 +115,7 @@ impl CommandRequest for StickerFindRequest { ); } - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(StickerFindRequest { sticker_type, diff --git a/src/commands/stickers/sticker_get.rs b/src/commands/stickers/sticker_get.rs index 1af3bd51..f637aae0 100644 --- a/src/commands/stickers/sticker_get.rs +++ b/src/commands/stickers/sticker_get.rs @@ -17,6 +17,8 @@ pub struct StickerGetRequest { impl CommandRequest for StickerGetRequest { const COMMAND: &'static str = "sticker get"; + const MIN_ARGS: u32 = 3; + const MAX_ARGS: Option = Some(3); fn into_request_enum(self) -> crate::Request { crate::Request::StickerGet(self.sticker_type, self.uri, self.name) @@ -44,22 +46,35 @@ impl CommandRequest for StickerGetRequest { } 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()))?; + let sticker_type = parts.next().ok_or(Self::missing_arguments_error(0))?; + let sticker_type = + sticker_type + .parse() + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 0, + expected_type: "StickerType".to_string(), + raw_input: sticker_type.to_string(), + })?; - let uri = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let uri = parts.next().ok_or(Self::missing_arguments_error(1))?; let uri = uri .parse() - .map_err(|_| RequestParserError::SyntaxError(1, uri.to_owned()))?; + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 1, + expected_type: "Uri".to_string(), + raw_input: uri.to_string(), + })?; - let name = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let name = parts.next().ok_or(Self::missing_arguments_error(2))?; let name = name .parse() - .map_err(|_| RequestParserError::SyntaxError(1, name.to_owned()))?; + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 2, + expected_type: "String".to_string(), + raw_input: name.to_string(), + })?; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(StickerGetRequest { sticker_type, diff --git a/src/commands/stickers/sticker_inc.rs b/src/commands/stickers/sticker_inc.rs index c0299619..0d087f36 100644 --- a/src/commands/stickers/sticker_inc.rs +++ b/src/commands/stickers/sticker_inc.rs @@ -18,6 +18,8 @@ pub struct StickerIncRequest { impl CommandRequest for StickerIncRequest { const COMMAND: &'static str = "sticker inc"; + const MIN_ARGS: u32 = 4; + const MAX_ARGS: Option = Some(4); fn into_request_enum(self) -> crate::Request { crate::Request::StickerInc(self.sticker_type, self.uri, self.name, self.value) @@ -47,27 +49,40 @@ impl CommandRequest for StickerIncRequest { } 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()))?; + let sticker_type = parts.next().ok_or(Self::missing_arguments_error(0))?; + let sticker_type = + sticker_type + .parse() + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 0, + expected_type: "StickerType".to_string(), + raw_input: sticker_type.to_string(), + })?; - let uri = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let uri = parts.next().ok_or(Self::missing_arguments_error(1))?; let uri = uri .parse() - .map_err(|_| RequestParserError::SyntaxError(1, uri.to_owned()))?; + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 1, + expected_type: "Uri".to_string(), + raw_input: uri.to_string(), + })?; - let name = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let name = parts.next().ok_or(Self::missing_arguments_error(2))?; let name = name .parse() - .map_err(|_| RequestParserError::SyntaxError(1, name.to_owned()))?; + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 2, + expected_type: "String".to_string(), + raw_input: name.to_string(), + })?; 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()); + Self::throw_if_too_many_arguments(parts)?; Ok(StickerIncRequest { sticker_type, diff --git a/src/commands/stickers/sticker_list.rs b/src/commands/stickers/sticker_list.rs index c0452122..675fb86c 100644 --- a/src/commands/stickers/sticker_list.rs +++ b/src/commands/stickers/sticker_list.rs @@ -19,6 +19,8 @@ pub struct StickerListRequest { impl CommandRequest for StickerListRequest { const COMMAND: &'static str = "sticker list"; + const MIN_ARGS: u32 = 2; + const MAX_ARGS: Option = Some(2); fn into_request_enum(self) -> crate::Request { crate::Request::StickerList(self.sticker_type, self.uri) @@ -38,17 +40,26 @@ impl CommandRequest for StickerListRequest { } 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()))?; + let sticker_type = parts.next().ok_or(Self::missing_arguments_error(0))?; + let sticker_type = + sticker_type + .parse() + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 0, + expected_type: "StickerType".to_string(), + raw_input: sticker_type.to_string(), + })?; - let uri = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let uri = parts.next().ok_or(Self::missing_arguments_error(1))?; let uri = uri .parse() - .map_err(|_| RequestParserError::SyntaxError(1, uri.to_owned()))?; + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 1, + expected_type: "Uri".to_string(), + raw_input: uri.to_string(), + })?; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(StickerListRequest { sticker_type, uri }) } diff --git a/src/commands/stickers/sticker_set.rs b/src/commands/stickers/sticker_set.rs index ed221c09..1748dd59 100644 --- a/src/commands/stickers/sticker_set.rs +++ b/src/commands/stickers/sticker_set.rs @@ -18,6 +18,8 @@ pub struct StickerSetRequest { impl CommandRequest for StickerSetRequest { const COMMAND: &'static str = "sticker set"; + const MIN_ARGS: u32 = 4; + const MAX_ARGS: Option = Some(4); fn into_request_enum(self) -> crate::Request { crate::Request::StickerSet(self.sticker_type, self.uri, self.name, self.value) @@ -47,24 +49,37 @@ impl CommandRequest for StickerSetRequest { } 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(0, sticker_type.to_owned()))?; + let sticker_type = parts.next().ok_or(Self::missing_arguments_error(0))?; + let sticker_type = + sticker_type + .parse() + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 0, + expected_type: "StickerType".to_string(), + raw_input: sticker_type.to_string(), + })?; - let uri = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let uri = parts.next().ok_or(Self::missing_arguments_error(1))?; let uri = uri .parse() - .map_err(|_| RequestParserError::SyntaxError(0, uri.to_owned()))?; + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 1, + expected_type: "Uri".to_string(), + raw_input: uri.to_string(), + })?; - let name = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let name = parts.next().ok_or(Self::missing_arguments_error(2))?; let name = name .parse() - .map_err(|_| RequestParserError::SyntaxError(0, name.to_owned()))?; + .map_err(|_| RequestParserError::SubtypeParserError { + argument_index: 2, + expected_type: "String".to_string(), + raw_input: name.to_string(), + })?; let value = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(StickerSetRequest { sticker_type, diff --git a/src/commands/stickers/stickernamestypes.rs b/src/commands/stickers/stickernamestypes.rs index 0314a04a..74e9ef26 100644 --- a/src/commands/stickers/stickernamestypes.rs +++ b/src/commands/stickers/stickernamestypes.rs @@ -16,6 +16,8 @@ pub struct StickerNamesTypesRequest(Option); impl CommandRequest for StickerNamesTypesRequest { const COMMAND: &'static str = "stickernamestypes"; + const MIN_ARGS: u32 = 0; + const MAX_ARGS: Option = Some(1); fn into_request_enum(self) -> crate::Request { crate::Request::StickerNamesTypes(self.0) @@ -48,7 +50,7 @@ impl CommandRequest for StickerNamesTypesRequest { }) .transpose()?; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(StickerNamesTypesRequest(sticker_type)) } diff --git a/src/commands/stored_playlists/listplaylist.rs b/src/commands/stored_playlists/listplaylist.rs index 07d1a468..1dca9846 100644 --- a/src/commands/stored_playlists/listplaylist.rs +++ b/src/commands/stored_playlists/listplaylist.rs @@ -22,6 +22,8 @@ pub struct ListPlaylistRequest { impl CommandRequest for ListPlaylistRequest { const COMMAND: &'static str = "listplaylist"; + const MIN_ARGS: u32 = 1; + const MAX_ARGS: Option = Some(2); fn into_request_enum(self) -> crate::Request { crate::Request::ListPlaylist(self.name, self.range) @@ -56,7 +58,7 @@ impl CommandRequest for ListPlaylistRequest { }) .transpose()?; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(ListPlaylistRequest { name, range }) } diff --git a/src/commands/stored_playlists/listplaylistinfo.rs b/src/commands/stored_playlists/listplaylistinfo.rs index 156c5491..a77ef5bc 100644 --- a/src/commands/stored_playlists/listplaylistinfo.rs +++ b/src/commands/stored_playlists/listplaylistinfo.rs @@ -17,6 +17,8 @@ pub struct ListPlaylistInfoRequest { impl CommandRequest for ListPlaylistInfoRequest { const COMMAND: &'static str = "listplaylistinfo"; + const MIN_ARGS: u32 = 1; + const MAX_ARGS: Option = Some(2); fn into_request_enum(self) -> crate::Request { crate::Request::ListPlaylistInfo(self.name, self.range) @@ -52,7 +54,7 @@ impl CommandRequest for ListPlaylistInfoRequest { }) .transpose()?; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(ListPlaylistInfoRequest { name, range }) } diff --git a/src/commands/stored_playlists/load.rs b/src/commands/stored_playlists/load.rs index adf05966..cac72d3e 100644 --- a/src/commands/stored_playlists/load.rs +++ b/src/commands/stored_playlists/load.rs @@ -17,6 +17,8 @@ pub struct LoadRequest { impl CommandRequest for LoadRequest { const COMMAND: &'static str = "load"; + const MIN_ARGS: u32 = 1; + const MAX_ARGS: Option = Some(3); fn into_request_enum(self) -> crate::Request { crate::Request::Load(self.name, self.range, self.position) @@ -53,7 +55,7 @@ impl CommandRequest for LoadRequest { fn parse(mut parts: RequestTokenizer<'_>) -> Result { let playlist_name = parts .next() - .ok_or(RequestParserError::UnexpectedEOF)? + .ok_or(Self::missing_arguments_error(0))? .to_string(); let mut range = None; @@ -73,7 +75,7 @@ impl CommandRequest for LoadRequest { } } - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(LoadRequest { name: playlist_name, diff --git a/src/commands/stored_playlists/playlistadd.rs b/src/commands/stored_playlists/playlistadd.rs index 319786ec..1cb9ad02 100644 --- a/src/commands/stored_playlists/playlistadd.rs +++ b/src/commands/stored_playlists/playlistadd.rs @@ -17,6 +17,8 @@ pub struct PlaylistAddRequest { impl CommandRequest for PlaylistAddRequest { const COMMAND: &'static str = "playlistadd"; + const MIN_ARGS: u32 = 2; + const MAX_ARGS: Option = Some(3); fn into_request_enum(self) -> crate::Request { crate::Request::PlaylistAdd(self.playlist_name, self.uri, self.position) @@ -53,12 +55,12 @@ impl CommandRequest for PlaylistAddRequest { fn parse(mut parts: RequestTokenizer<'_>) -> Result { let playlist_name = parts .next() - .ok_or(RequestParserError::UnexpectedEOF)? + .ok_or(Self::missing_arguments_error(0))? .to_string(); let uri = parts .next() - .ok_or(RequestParserError::UnexpectedEOF)? + .ok_or(Self::missing_arguments_error(1))? .to_string(); let position = parts @@ -69,7 +71,7 @@ impl CommandRequest for PlaylistAddRequest { }) .transpose()?; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(PlaylistAddRequest { playlist_name, diff --git a/src/commands/stored_playlists/playlistdelete.rs b/src/commands/stored_playlists/playlistdelete.rs index 015f675f..7d92c23a 100644 --- a/src/commands/stored_playlists/playlistdelete.rs +++ b/src/commands/stored_playlists/playlistdelete.rs @@ -16,6 +16,8 @@ pub struct PlaylistDeleteRequest { impl CommandRequest for PlaylistDeleteRequest { const COMMAND: &'static str = "playlistdelete"; + const MIN_ARGS: u32 = 2; + const MAX_ARGS: Option = Some(2); fn into_request_enum(self) -> crate::Request { crate::Request::PlaylistDelete(self.playlist_name, self.position) @@ -45,7 +47,7 @@ impl CommandRequest for PlaylistDeleteRequest { fn parse(mut parts: RequestTokenizer<'_>) -> Result { let playlist_name = parts .next() - .ok_or(RequestParserError::UnexpectedEOF)? + .ok_or(Self::missing_arguments_error(0))? .to_string(); // TODO: this can be a range, according to docs @@ -54,7 +56,7 @@ impl CommandRequest for PlaylistDeleteRequest { .parse() .map_err(|_| RequestParserError::SyntaxError(0, position.to_string()))?; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(PlaylistDeleteRequest { playlist_name, diff --git a/src/commands/stored_playlists/playlistmove.rs b/src/commands/stored_playlists/playlistmove.rs index df01ab16..69057af8 100644 --- a/src/commands/stored_playlists/playlistmove.rs +++ b/src/commands/stored_playlists/playlistmove.rs @@ -17,6 +17,8 @@ pub struct PlaylistMoveRequest { impl CommandRequest for PlaylistMoveRequest { const COMMAND: &'static str = "playlistmove"; + const MIN_ARGS: u32 = 2; + const MAX_ARGS: Option = Some(3); fn into_request_enum(self) -> crate::Request { crate::Request::PlaylistMove(self.playlist_name, self.from, self.to) @@ -53,7 +55,7 @@ impl CommandRequest for PlaylistMoveRequest { fn parse(mut parts: RequestTokenizer<'_>) -> Result { let playlist_name = parts .next() - .ok_or(RequestParserError::UnexpectedEOF)? + .ok_or(Self::missing_arguments_error(0))? .to_string(); let mut from = None; @@ -61,7 +63,7 @@ impl CommandRequest for PlaylistMoveRequest { let to = parts.next(); - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; let to = if let Some(to) = to { from = Some(from_or_range_or_to.parse().map_err(|_| { diff --git a/src/commands/stored_playlists/rename.rs b/src/commands/stored_playlists/rename.rs index 17191db6..252d21c8 100644 --- a/src/commands/stored_playlists/rename.rs +++ b/src/commands/stored_playlists/rename.rs @@ -15,6 +15,8 @@ pub struct RenameRequest { impl CommandRequest for RenameRequest { const COMMAND: &'static str = "rename"; + const MIN_ARGS: u32 = 2; + const MAX_ARGS: Option = Some(2); fn into_request_enum(self) -> crate::Request { crate::Request::Rename(self.old_name, self.new_name) @@ -36,15 +38,15 @@ impl CommandRequest for RenameRequest { fn parse(mut parts: RequestTokenizer<'_>) -> Result { let old_name = parts .next() - .ok_or(RequestParserError::UnexpectedEOF)? + .ok_or(Self::missing_arguments_error(0))? .to_string(); let new_name = parts .next() - .ok_or(RequestParserError::UnexpectedEOF)? + .ok_or(Self::missing_arguments_error(1))? .to_string(); - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(RenameRequest { old_name, new_name }) } diff --git a/src/commands/stored_playlists/save.rs b/src/commands/stored_playlists/save.rs index f0a5aa0f..2cac474e 100644 --- a/src/commands/stored_playlists/save.rs +++ b/src/commands/stored_playlists/save.rs @@ -16,6 +16,8 @@ pub struct SaveRequest { impl CommandRequest for SaveRequest { const COMMAND: &'static str = "save"; + const MIN_ARGS: u32 = 2; + const MAX_ARGS: Option = Some(3); fn into_request_enum(self) -> crate::Request { crate::Request::Save(self.playlist_name, self.mode) @@ -45,7 +47,7 @@ impl CommandRequest for SaveRequest { fn parse(mut parts: RequestTokenizer<'_>) -> Result { let playlist_name = parts .next() - .ok_or(RequestParserError::UnexpectedEOF)? + .ok_or(Self::missing_arguments_error(0))? .to_string(); let mode = parts @@ -56,7 +58,7 @@ impl CommandRequest for SaveRequest { }) .transpose()?; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(SaveRequest { playlist_name, diff --git a/src/commands/stored_playlists/searchplaylist.rs b/src/commands/stored_playlists/searchplaylist.rs index 7a686330..c46763a1 100644 --- a/src/commands/stored_playlists/searchplaylist.rs +++ b/src/commands/stored_playlists/searchplaylist.rs @@ -18,6 +18,8 @@ pub struct SearchPlaylistRequest { impl CommandRequest for SearchPlaylistRequest { const COMMAND: &'static str = "searchplaylist"; + const MIN_ARGS: u32 = 2; + const MAX_ARGS: Option = Some(3); fn into_request_enum(self) -> crate::Request { crate::Request::SearchPlaylist(self.name, self.filter, self.range) @@ -44,7 +46,7 @@ impl CommandRequest for SearchPlaylistRequest { } fn parse(mut parts: RequestTokenizer<'_>) -> Result { - let name = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let name = parts.next().ok_or(Self::missing_arguments_error(0))?; let name = name .parse::() .map_err(|_| RequestParserError::SyntaxError(0, name.to_owned()))?; @@ -64,7 +66,7 @@ impl CommandRequest for SearchPlaylistRequest { }) .transpose()?; - debug_assert!(parts.next().is_none()); + Self::throw_if_too_many_arguments(parts)?; Ok(SearchPlaylistRequest { name,