blob: 268e38a31cdc6637baddd576bb2c4cd80a0238ad [file] [log] [blame]
//! # Chapter 6: Error Reporting
//!
//! ## `Error`
//!
//! Back in [`chapter_1`], we glossed over the `Err` side of [`IResult`]. `IResult<I, O>` is
//! actually short for `IResult<I, O, E=Error>` where [`Error`] is a cheap, universal error type
//! for getting started. When humans are producing the file, like with `toml`, you might want to
//! sacrifice some performance for providing more details on how to resolve the problem
//!
//! winnow includes [`VerboseError`] for this but you can [customize the error as you
//! wish][_topic::error]. You can use [`Parser::context`] to annotate the error with custom types
//! while unwinding to further improve the error quality.
//!
//! ```rust
//! # use winnow::IResult;
//! # use winnow::Parser;
//! # use winnow::token::take_while;
//! # use winnow::combinator::alt;
//! use winnow::error::VerboseError;
//!
//! fn parse_digits(input: &str) -> IResult<&str, (&str, &str), VerboseError<&str>> {
//! alt((
//! ("0b", parse_bin_digits).context("binary"),
//! ("0o", parse_oct_digits).context("octal"),
//! ("0d", parse_dec_digits).context("decimal"),
//! ("0x", parse_hex_digits).context("hexadecimal"),
//! )).parse_next(input)
//! }
//!
//! // ...
//! # fn parse_bin_digits(input: &str) -> IResult<&str, &str, VerboseError<&str>> {
//! # take_while(1.., (
//! # ('0'..='7'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_oct_digits(input: &str) -> IResult<&str, &str, VerboseError<&str>> {
//! # take_while(1.., (
//! # ('0'..='7'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_dec_digits(input: &str) -> IResult<&str, &str, VerboseError<&str>> {
//! # take_while(1.., (
//! # ('0'..='9'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_hex_digits(input: &str) -> IResult<&str, &str, VerboseError<&str>> {
//! # take_while(1.., (
//! # ('0'..='9'),
//! # ('A'..='F'),
//! # ('a'..='f'),
//! # )).parse_next(input)
//! # }
//!
//! fn main() {
//! let input = "0x1a2b Hello";
//!
//! let (remainder, (prefix, digits)) = parse_digits.parse_next(input).unwrap();
//!
//! assert_eq!(remainder, " Hello");
//! assert_eq!(prefix, "0x");
//! assert_eq!(digits, "1a2b");
//! }
//! ```
//!
//! At first glance, this looks correct but what `context` will be reported when parsing `"0b5"`?
//! If you remember back to [`chapter_3`], [`alt`] will only report the last error by default which
//! means when parsing `"0b5"`, the `context` will be `"hexadecimal"`.
//!
//! ## `ErrMode`
//!
//! Let's break down `IResult<I, O, E>` one step further:
//! ```rust
//! # use winnow::error::Error;
//! # use winnow::error::ErrMode;
//! pub type IResult<I, O, E = Error<I>> = Result<(I, O), ErrMode<E>>;
//! ```
//! `IResult` is just a fancy wrapper around `Result` that wraps our error in an [`ErrMode`]
//! type.
//!
//! `ErrMode` is an enum with `Backtrack` and `Cut` variants (ignore `Incomplete` as its only
//! relevant for [streaming][_topic::stream]). By default, errors are `Backtrack`, meaning that
//! other parsing branches will be attempted on failure, like the next case of an `alt`. `Cut`
//! shortcircuits all other branches, immediately reporting the error.
//!
//! So we can get the correct `context` by modifying the above example with [`cut_err`]:
//! ```rust
//! # use winnow::IResult;
//! # use winnow::Parser;
//! # use winnow::token::take_while;
//! # use winnow::combinator::alt;
//! # use winnow::error::VerboseError;
//! use winnow::combinator::cut_err;
//!
//! fn parse_digits(input: &str) -> IResult<&str, (&str, &str), VerboseError<&str>> {
//! alt((
//! ("0b", cut_err(parse_bin_digits)).context("binary"),
//! ("0o", cut_err(parse_oct_digits)).context("octal"),
//! ("0d", cut_err(parse_dec_digits)).context("decimal"),
//! ("0x", cut_err(parse_hex_digits)).context("hexadecimal"),
//! )).parse_next(input)
//! }
//!
//! // ...
//! # fn parse_bin_digits(input: &str) -> IResult<&str, &str, VerboseError<&str>> {
//! # take_while(1.., (
//! # ('0'..='7'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_oct_digits(input: &str) -> IResult<&str, &str, VerboseError<&str>> {
//! # take_while(1.., (
//! # ('0'..='7'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_dec_digits(input: &str) -> IResult<&str, &str, VerboseError<&str>> {
//! # take_while(1.., (
//! # ('0'..='9'),
//! # )).parse_next(input)
//! # }
//! #
//! # fn parse_hex_digits(input: &str) -> IResult<&str, &str, VerboseError<&str>> {
//! # take_while(1.., (
//! # ('0'..='9'),
//! # ('A'..='F'),
//! # ('a'..='f'),
//! # )).parse_next(input)
//! # }
//!
//! fn main() {
//! let input = "0x1a2b Hello";
//!
//! let (remainder, (prefix, digits)) = parse_digits.parse_next(input).unwrap();
//!
//! assert_eq!(remainder, " Hello");
//! assert_eq!(prefix, "0x");
//! assert_eq!(digits, "1a2b");
//! }
//! ```
//! Now, when parsing `"0b5"`, the `context` will be `"binary"`.
#![allow(unused_imports)]
use super::chapter_1;
use super::chapter_3;
use crate::combinator::alt;
use crate::combinator::cut_err;
use crate::error::ErrMode;
use crate::error::Error;
use crate::error::VerboseError;
use crate::IResult;
use crate::Parser;
use crate::_topic;
pub use super::chapter_5 as previous;
pub use super::chapter_7 as next;