commands: add and fix variants for RequestParserError

This commit is contained in:
2025-12-08 13:55:25 +09:00
parent cbd2a77c61
commit a8ad38adf2
2 changed files with 120 additions and 22 deletions

View File

@@ -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<u32>;
/// 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<Self, crate::commands::RequestParserError> {
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<Self, crate::commands::RequestParserError> {
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")]

View File

@@ -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),