Nom parser incorrectly succeeds with invalid input

Hi everyone.

I've two nom parsers for redis RESP SET command input

fn expiry_to_timestamp(expiry: &ExpiryOption) -> anyhow::Result<u64> {
    // u64 always since u32 secs fits into u64
    // get the current system time
    let now = SystemTime::now();

    // how many seconds have elapsed since beginning of time
    let duration_since_epoch = now
        .duration_since(UNIX_EPOCH)
        .context("This should always succeed")?;

    // we don't want to lose precision between seconds & milliseconds
    match expiry {
        ExpiryOption::Seconds(seconds) => Ok(*seconds as u64 + duration_since_epoch.as_secs()),
        ExpiryOption::Milliseconds(milliseconds) => {
            Ok(milliseconds + duration_since_epoch.as_millis() as u64) // nominally millis are u128
        }
    }
}
          map_res(
                tuple((tag_no_case("$2\r\nEX\r\n"), parse_resp_string)),
                |(_, seconds_str)| {
                    seconds_str
                        .parse::<u32>()
                        .map_err(|_e: ParseIntError| {
                            nom::error::Error::new(
                                seconds_str.clone(),
                                nom::error::ErrorKind::Digit,
                            )
                        })
                        .and_then(|seconds| {
                            expiry_to_timestamp(&ExpiryOption::Seconds(seconds))
                                .map_err(|_| {
                                    nom::error::Error::new(
                                        seconds_str.clone(),
                                        nom::error::ErrorKind::Verify,
                                    )
                                })
                                .map(|timestamp| SetCommandExpireOption::EX(timestamp as u32))
                        })
                },
            ),

and the problem is, even if I give it SET foo bar ex blah, i.e. telling it to expire foo in blah seconds, it still returns "OK".

Now, if I remove all those map_err and just do an expect then it correctly fails but it also panics, severing my client connection.

There are some suggestions from random google searches, that perhaps the issue here is that map_res is not failing the parser when the inner Result is an Err. Instead, it's creating a nom error but not propagating it correctly. Prob because map_res expects the error to be returned as part of the IResult type, not as a nom::error::Error

But now I'm getting throughly lost on all this: how do I make it fail as it is supposed to?

Can you show the full code? We can't help you if you only show part of it.

Yes, good point sorry redis/src/parsers.rs at 77-better-error-handling · devfire/redis · GitHub

It's the usual cargo run in one window and then redis-cli set a b ex invalid in another.

I'm having trouble understanding it, partially because I've never used nom, but also because there are no intermediate variables, just one big expression. If you break it into pieces with intermediate variables, at least during debugging, then you can look at the variables' types in the IDE and also print their values or look at them in a debugger. Or maybe you already know all the types of those sub-expressions, so this wouldn't help? I'm just wondering if you're lost because the expression is too big.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.