70 %
Chris Biscardi

Custom Error types with Nom 5 in Rust

The nom custom error example gave me a bit of trouble until Geal was kind enough to point out a couple options.

tldr you need to implement ParseError and From for your type, then use .map_err(Err::convert) because ? on it's own doesn't work without type specialization.

Here's a custom error implementation.

rust
#[derive(Debug, PartialEq)]
pub enum MDXError<I> {
MyError,
Nom(I, ErrorKind),
}
impl<'a> From<(&'a [u8], ErrorKind)> for MDXError<&'a [u8]> {
fn from((i, ek): (&'a [u8], ErrorKind)) -> Self {
MDXError::Nom(i, ek)
}
}
impl<I> ParseError<I> for MDXError<I> {
fn from_error_kind(input: I, kind: ErrorKind) -> Self {
MDXError::Nom(input, kind)
}
fn append(_: I, _: ErrorKind, other: Self) -> Self {
other
}
}

And the program that uses it:

rust
use nom::{
bytes::complete,
character::*,
error::{ErrorKind, ParseError},
Err::Error,
IResult, *,
};
// The datatypes we're parsing into
#[derive(Debug, PartialEq, Eq)]
struct ATXHeading<'a> {
level: usize,
value: &'a [u8],
}
struct SEHeading {}
enum Heading<'a> {
ATXHeading(&'a ATXHeading<'a>),
SEHeading(SEHeading),
}
// A couple of named parsers, built with macros
named!(hashtags, is_a!("#"));
named!(alpha, take_while!(is_alphanumeric));
named!(spaces, take_while!(is_space));
// parsing Markdown ATX headings, for which too many #'s fails
fn atx_heading(
input: &[u8],
) -> IResult<&[u8], ATXHeading, MDXError<&[u8]>> {
let (input, hashes) =
hashtags(input).map_err(Err::convert)?;
if hashes.len() > 6 {
return Err(Error(MDXError::MyError));
}
let (input, _) = spaces(input).map_err(Err::convert)?;
let (input, val) = nom::bytes::complete::take_while(
is_alphanumeric,
)(input)?;
Ok((
input,
ATXHeading {
level: hashes.len(),
value: val,
},
))
}
// a test to run to check if it's working
#[test]
fn parse_mdx() {
assert_eq!(
atx_heading(
b"# boop
"
),
Ok((
"
"
.as_bytes(),
ATXHeading {
level: 1,
value: b"boop"
}
))
);
}

Note specifically the use of the type signature for atx_heading:

rust
fn atx_heading(input: &[u8]) -> IResult<&[u8], ATXHeading, MDXError<&[u8]>> {

and the ability to return the custom error variant:

rust
if hashes.len() > 6 {
return Err(Error(MDXError::MyError));
}