diff --git a/src/commands.rs b/src/commands.rs index 533e0d7..88912ee 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -56,6 +56,13 @@ where /// The command name used within the protocol 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 maximum number of arguments this command takes + // const MAX_ARGS: Option; + /// Converts this specific request type to it's corresponding variant in the generic Request enum. fn into_request_enum(self) -> crate::Request; @@ -256,7 +263,17 @@ macro_rules! empty_command_request { fn parse( mut parts: crate::commands::RequestTokenizer<'_>, ) -> Result { - debug_assert!(parts.next().is_none()); + 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), + }); + } Ok(paste::paste! { [<$name Request>] }) } @@ -323,14 +340,34 @@ macro_rules! single_item_command_request { fn parse( mut parts: crate::commands::RequestTokenizer<'_>, ) -> Result { - let item_token = parts - .next() - .ok_or(crate::commands::RequestParserError::UnexpectedEOF)?; + let item_token = + parts + .next() + .ok_or(crate::commands::RequestParserError::MissingArguments { + expected_min: 1, + expected_max: 1, + found: 0, + })?; + let item = item_token.parse::<$item_type>().map_err(|_| { - crate::commands::RequestParserError::SyntaxError(0, item_token.to_owned()) + crate::commands::RequestParserError::SubtypeParserError { + argument_index: 1, + expected_type: stringify!($item_type).to_string(), + raw_input: item_token.to_owned(), + } })?; - debug_assert!(parts.next().is_none()); + 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), + }); + } Ok(paste::paste! { [<$name Request>] ( item ) }) } @@ -378,12 +415,27 @@ macro_rules! single_optional_item_command_request { .next() .map(|s| { s.parse().map_err(|_| { - crate::commands::RequestParserError::SyntaxError(0, s.to_owned()) + crate::commands::RequestParserError::SubtypeParserError { + argument_index: 1, + expected_type: stringify!($item_type).to_string(), + raw_input: s.to_owned(), + } }) }) .transpose()?; - debug_assert!(parts.next().is_none()); + 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), + }); + } Ok(paste::paste! { [<$name Request>] ( item ) }) } @@ -491,18 +543,68 @@ pub(crate) use single_optional_item_command_request; #[derive(Error, Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum RequestParserError { + #[error("Found empty line while parsing request")] + EmptyLine, + + // TODO: remove this, replaced by various other errors #[error("Could not parse the request due to a syntax error at position {0}: {1}")] SyntaxError(u64, String), - #[error("A command list was expected to be closed, but the end was not found at line {0}")] - MissingCommandListEnd(u64), + #[error( + "Could not parse argument {argument_index} of the request (expected type: {expected_type}, raw input: '{raw_input}')" + )] + SubtypeParserError { + /// The index of the argument that failed to parse + argument_index: u32, - #[error("A command list was found inside another command list at line {0}")] - NestedCommandList(u64), + /// The expected type of the argument + expected_type: String, - #[error("A command list was closed unexpectedly at line {0}")] - UnexpectedCommandListEnd(u64), + /// The raw input that failed to parse + raw_input: String, + }, + #[error( + "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, + + /// 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})" + )] + MissingArguments { + /// The minimum number of arguments that were expected + expected_min: u32, + + /// The maximum number of arguments that were expected + expected_max: u32, + + /// The number of arguments that were found + found: u32, + }, + + #[error("A command list was expected to be closed, but the end was not found")] + MissingCommandListEnd, + + #[error("A command list was found inside another command list at line {line}")] + NestedCommandList { + /// The line where the nested command list was found + line: u32, + }, + + #[error("An unexpected command list end was found")] + UnexpectedCommandListEnd, + + // TODO: remove this, replaced by EmptyLine + MissingArguments #[error("Request ended early, while more arguments were expected")] UnexpectedEOF, // #[error("Request is missing terminating newline")] diff --git a/src/request.rs b/src/request.rs index ff7e7c5..49bcd8e 100644 --- a/src/request.rs +++ b/src/request.rs @@ -399,11 +399,7 @@ impl Request { .ok_or(RequestParserError::UnexpectedEOF)?; let mut parts = RequestTokenizer::new(line); - match parts - .next() - .ok_or(RequestParserError::SyntaxError(0, line.to_string()))? - .trim() - { + match parts.next().ok_or(RequestParserError::EmptyLine)?.trim() { "command_list_begin" => { let mut commands = Vec::new(); let mut i = 1; @@ -411,10 +407,10 @@ impl Request { i += 1; let (line, _rest) = rest .split_once('\n') - .ok_or(RequestParserError::MissingCommandListEnd(i))?; + .ok_or(RequestParserError::MissingCommandListEnd)?; match line.trim() { "command_list_begin" => { - return Err(RequestParserError::NestedCommandList(i)); + return Err(RequestParserError::NestedCommandList { line: i }); } "command_list_end" => { return Ok(Request::CommandList(commands)); @@ -426,7 +422,7 @@ impl Request { } } } - "command_list_end" => Err(RequestParserError::UnexpectedCommandListEnd(0)), + "command_list_end" => Err(RequestParserError::UnexpectedCommandListEnd), /* querying mpd status */ ClearErrorRequest::COMMAND => parse_req!(ClearErrorRequest, parts),