Writing tests for nom parsers with VerboseError and convert_error - rust

I'm trying to write tests for nom parsers that provide better messages when they fail.
I have a macro similar to assert_eq! called assert_parses_to! that I want to provide a user-friendly display string when the parser fails:
#[macro_export]
macro_rules! assert_parses_to {
($parser:ident, $input:expr, $expectation:expr) => {
match (&$parser($input), &$expectation) {
(Err(candidate_error), expectation_val) => {
panic!(
"Failed to parse the candidate\n\
{}",
convert_error($input, *candidate_error)
);
}
(Ok(candidate_val), expectation_val) => {
if !(*candidate_val == *expectation_val) {
panic!(
"Failed to parse to expected value\n\
Got: {:?}\n\
Expected: {:?}",
candidate_val, expectation_val
)
}
}
}
};
}
However, when trying to compile, I get this error:
error[E0308]: mismatched types
--> src/parser/tests.rs:27:43
|
27 | convert_error($input, *candidate_error)
| ------------- ^^^^^^^^^^^^^^^^ expected struct `VerboseError`, found enum `nom::Err`
| |
| arguments to this function are incorrect
|
::: src/parser/ansi_2016/lexical.rs:237:9
|
237 | / assert_parses_to!(
238 | | regular_identifier,
239 | | "identifier ",
240 | | (
... |
246 | | )
247 | | )
| |_________- in this macro invocation
|
= note: expected struct `VerboseError<&str>`
found enum `nom::Err<VerboseError<&str>>`
I'm assuming that I need to modify the match statement to extract the VerboseError from the error, but I haven't figured out how.
I'm new to rust and nom - any help appreciated!

Resolved by matching on the inner value of the Err enum:
#[macro_export]
macro_rules! assert_parses_to {
($parser:ident, $input:expr, $expectation:expr) => {
match ($parser($input), $expectation) {
// Deeper match here
(Err(::nom::Err::Error(e) | ::nom::Err::Failure(e)), _) => {
panic!("{}", ::nom::error::convert_error($input, e));
}
(Err(e), _) => {
panic!("Failed to parse {:?}", e)
}
(Ok(candidate_val), expected_val) => {
if !(candidate_val == expected_val) {
panic!(
"Failed to parse to expected value\n\
Got: {:?}\n\
Expected: {:?}",
candidate_val, expected_val
)
}
}
}
};
}

Related

How to return both success and error case in the same Axum handler?

Ok so I have an axum handler which looks somewhat like this:
#[debug_handler]
async fn handler(
State(server_state): State<Arc<Server>>,
Query(query_params): Query<Query>,
) -> impl IntoResponse {
match server_state.store.handle(query_params).await {
Ok(res) => (StatusCode::OK, Json(res)),
Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, Json(err))
}
}
This fails with the following error:
|
42 | / match server_state.store.handle(query_params).await {
43 | | Ok(res) => (StatusCode::OK, Json(res)),
| | -------------------------- this is found to be of type `(StatusCode, axum::Json<Vec<Data>>)`
44 | | Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, Json(err))
| | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `Vec`, found enum `sqlx::Error`
45 | | }
| |_____- `match` arms have incompatible types
|
= note: expected tuple `(StatusCode, axum::Json<Vec<Data>>)`
found tuple `(StatusCode, axum::Json<sqlx::Error>)`
I understand why the error is happening. The two arms of the match expression don't have the same return type.
But the question is how to fix this? I am not sure it makes sense to have to convert somehow sqlx::Error into the ok case.
The other approach I can think of is to have a response struct...something like
struct Response {
body: Option<Data>,
error: Option<sqlx::Error>
}
and error will be None in case of success. body will be None in case of error.
The question is, I am not sure if this is the generally acceptable way of handling this with Axum?
a little bit a late answer, but i think the easiest way to do it in this case is to apply into_response() to both match-arm results:
match server_state.store.handle(query_params).await {
Ok(res) => (StatusCode::OK, Json(res)).into_response(),
Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, Json(err)).into_response()
}
Best regards Thomas

async syntactic oddity

Why does Rust allow this:
fn main() {
let f = |x: bool| {
async {
return
}
};
}
But not this? Specifically, the error complains that the branches return different types, when they appear to be exactly the same.
fn main() {
let f = |x: bool| {
if true {
async {
return
}
} else {
async {
return
}
}
};
}
error[E0308]: `if` and `else` have incompatible types
--> src/main.rs:42:13
|
37 | / if true {
38 | | async {
| _|_____________-
39 | | | return
40 | | | }
| |_|_____________- expected because of this
41 | | } else {
42 | / | async {
43 | | | return
44 | | | }
| |_|_____________^ expected `async` block, found a different `async` block
45 | | }
| |_________- `if` and `else` have incompatible types
|
Every time you write async { }, the compiler makes a unique anonymous Future type. They will be distinct even if two are syntactically equivalent. This is the same for closures.
So your first snippet is simply returning an object (with an anonymous type), while your second is trying to return different types conditionally, which is not allowed. Consider using a trait object so they are the same type:
use std::future::Future;
fn main() {
let f = |x: bool| {
if true {
Box::new(async {
return
}) as Box<dyn Future<Output=()>>
} else {
Box::new(async {
return
})
}
};
}

How to declare a variable in `macro_rules!`?

I'm creating a macro called throw_error. I expected this to compile yet, it fails:
// Util structs + types
...
// Util macros
#[macro_export]
macro_rules! throw_error {
() => {
RaptorexError {
message: String::new(),
line: line!(),
file: file!().to_owned(),
}
};
($($msg:tt),*) => {
let mut final_msg = String::new();
$(
final_msg.push_str(&format!("{} ", $msg));
)*
// remove trailing whitespace
final_msg.pop();
RaptorexError {
message: final_msg,
line: line!(),
file: file!(),
}
}
}
// Util functions
...
I get several errors from using the macro in my other code.
Erros:
error: macro expansion ignores token `final_msg` and any following
--> /Users/henryboisdequin/Desktop/raptorex/raptorex_util/src/lib.rs:30:13
|
30 | final_msg.push_str(&format!("{} ", $msg));
| ^^^^^^^^^
|
::: compiler/src/parser/parser.rs:44:29
|
44 | _ => return Err(throw_error!("Unexpected token:", current_token)),
| ------------------------------------------------- help: you might be missing a semicolon here: `;`
| |
| caused by the macro expansion here
|
= note: the usage of `throw_error!` is likely invalid in expression context
error[E0658]: `let` expressions in this position are experimental
--> compiler/src/parser/parser.rs:44:29
|
44 | _ => return Err(throw_error!("Unexpected token:", current_token)),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: see issue #53667 <https://github.com/rust-lang/rust/issues/53667> for more information
= help: add `#![feature(let_chains)]` to the crate attributes to enable
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: `let` expressions are not supported here
--> compiler/src/parser/parser.rs:44:29
|
44 | _ => return Err(throw_error!("Unexpected token:", current_token)),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: only supported directly in conditions of `if`- and `while`-expressions
= note: as well as when nested within `&&` and parenthesis in those conditions
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
warning: unused imports: `DATA_TYPES`, `KEYWORDS`
--> compiler/src/parser/parser.rs:3:28
|
3 | lexer::tokens::{Token, DATA_TYPES, KEYWORDS},
| ^^^^^^^^^^ ^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
error[E0308]: mismatched types
--> compiler/src/parser/parser.rs:44:29
|
44 | _ => return Err(throw_error!("Unexpected token:", current_token)),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `raptorex_util::RaptorexError`, found `bool`
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to 4 previous errors; 1 warning emitted
What is the cause of these errors and how can I fix it?
You need another set of {}s so the macro creates a block containing the statements instead of the individual statements themselves:
#[macro_export]
macro_rules! throw_error {
() => {
RaptorexError {
message: String::new(),
line: line!(),
file: file!().to_owned(),
}
};
($($msg:tt),*) => {
{ // <------------------
let mut final_msg = String::new();
$(
final_msg.push_str(&format!("{} ", $msg));
)*
// remove trailing whitespace
final_msg.pop();
RaptorexError {
message: final_msg,
line: line!(),
file: file!(),
}
} // <-------------------
}
}
The {} in the (...) => {} is part of the macro syntax and isn't part of the generated code.

How to handle exception (Err) in unwrap_or_else?

struct OverflowError {}
fn test_unwrap() -> Result<String, OverflowError> {
let a: Result<String, u8> = Err(100);
let a: String = a.unwrap_or_else(|err| {
if err < 100 {
String::from("Ok")
} else {
// I want to return from the function (not just the closure)
// This is compile error with error:
// "the ? operator can only be used in a closure that returns Result or Option"
Err(OverflowError {})?
}
});
Ok(a)
}
error[E0277]: the `?` operator can only be used in a closure that returns `Result` or `Option` (or another type that implements `std::ops::Try`)
--> src/lib.rs:13:13
|
6 | let a: String = a.unwrap_or_else(|err| {
| ______________________________________-
7 | | if err < 100 {
8 | | String::from("Ok")
9 | | } else {
... |
13 | | Err(OverflowError {})?
| | ^^^^^^^^^^^^^^^^^^^^^^ cannot use the `?` operator in a closure that returns `std::string::String`
14 | | }
15 | | });
| |_____- this function should return `Result` or `Option` to accept `?`
|
= help: the trait `std::ops::Try` is not implemented for `std::string::String`
= note: required by `std::ops::Try::from_error`
It's the simplified version of my code. Basically inside the unwrap_or_else closure, there might be a conditional error (e.g. IOError). In such cases, I'd like to terminate the function early (using ?). But obviously it doesn't work since it is currently in a closure and the closure doesn't expect a Result type.
What's the best practice to handle this?
What you want is or_else():
struct OverflowError {}
fn test_unwrap() -> Result<String, OverflowError> {
let a: Result<String, u8> = Err(100);
let a: String = a.or_else(|err| {
if err < 100 {
Ok(String::from("Ok"))
} else {
Err(OverflowError {})
}
})?;
Ok(a)
}
Simplified:
struct OverflowError {}
fn test_unwrap() -> Result<String, OverflowError> {
Err(100).or_else(|err| {
if err < 100 {
Ok(String::from("Ok"))
} else {
Err(OverflowError {})
}
})
}

What is the right way to return a simple custom error with nom errorkind?

A simple error handler added to nom
The first one compiles with no errors, the second one errors out
use nom::*;
use std::str;
// This works
named!(
field<&str>,
map!(
complete!(add_return_error!(
ErrorKind::Custom(1),
preceded!(
tag!("."),
recognize!(many1!(
alt!(alphanumeric => { |_| true } | char!('_') => {|_| true})
))
)
)),
|v| str::from_utf8(v).unwrap()
)
);
// This doesn't compile
named!(
entity<&str>,
add_return_error!(
ErrorKind::Custom(2),
map!(
recognize!(many1!(
alt!(alphanumeric => { |_| true } | char!('_') => {|_| true})
)),
|v| str::from_utf8(v).unwrap()
)
)
);
Error is
cargo build
Compiling pdl_parser v0.1.0 (file:///H:/parser)
error[E0282]: type annotations needed
--> src\pdl_parser.rs:72:1
|
72 | / named!(
73 | | entity<&str>,
74 | | add_return_error!(
75 | | ErrorKind::Custom(2),
... |
82 | | )
83 | | );
| |__^ cannot infer type for `E`
|
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
Why does the first one work and second one not? And what can I do to fix it? I tried changing the signature of the second one to entity<&[u8],&str,ErrorKind> and then to i32 and u32 but no success.
The solution to the cannot infer type for E error is to separate out the parsing steps so the compiler does not get so confused.
Rewriting the parser-combinator as
named!(
entity_name<Vec<u8>>,
many1!(alt!(alphanumeric => { |x : &[u8]| x[0] } | char!('_') => {|_| b'_'}))
);
named!(
entity<&[u8],&str,i32>,
add_return_error!(
ErrorKind::Custom(2),
map!(recognize!(entity_name),|v| str::from_utf8(v).unwrap())
)
);
And the corresponding test
#[test]
fn parse_pdl_error_test() {
assert_eq!(entity(b"_error_stack").to_result(), Ok("_error_stack"));
assert_eq!(entity(b"[];']").to_result(), Err(ErrorKind::Custom(2)));
}

Resources