vest_lib 0.2.2

Verified parser and serializer combinators in Verus
Documentation
//! Runtime parse errors.
use core::fmt;

#[cfg(feature = "error-trace")]
use alloc::vec::Vec;

use vstd::prelude::*;

verus! {

/// A minimal runtime parser failure.
///
/// Vest parsers work on progressively sliced inputs, so a globally meaningful byte offset is not
/// available unless the caller explicitly threads that information through the parser stack.
/// Instead, this error carries a coarse-grained failure kind plus the name of the innermost
/// DSL-defined format that failed. With the `error-trace` feature it also retains the full
/// named-format call stack (at the cost of heap allocation).
#[derive(Debug, PartialEq, Eq)]
pub struct ParseError {
    /// The kind of failure that occurred.
    pub kind: ParseErrorKind,
    /// The innermost named format known to have failed.
    pub failed_format: Option<&'static str>,
    /// The named-format call stack that led to the failure, stored innermost-first.
    #[cfg(feature = "error-trace")]
    pub format_stack: Vec<&'static str>,
}

impl Clone for ParseError {
    fn clone(&self) -> Self {
        Self {
            kind: self.kind.clone(),
            failed_format: self.failed_format,
            #[cfg(feature = "error-trace")]
            format_stack: self.format_stack.clone(),
        }
    }
}

impl ParseError {
    /// Creates a new parse error.
    pub fn new(kind: ParseErrorKind) -> (e: Self)
        ensures
            e.kind == kind,
    {
        Self {
            kind,
            failed_format: None,
            #[cfg(feature = "error-trace")]
            format_stack: Vec::new(),
        }
    }

    /// Pushes one named format onto the failing format stack.
    ///
    /// The first pushed format becomes `current_format`, so `current_format` continues to refer to
    /// the innermost failing named format even as outer formats append themselves during
    /// propagation.
    pub fn push_format(self, current_format: &'static str) -> Self {
        let mut err = self;
        if err.failed_format.is_none() {
            err.failed_format = Some(current_format);
        }
        #[cfg(feature = "error-trace")]
        {
            err.format_stack.push(current_format);
        }
        err
    }

    /// Returns the recorded format stack as an innermost-first slice.
    pub fn format_trace(&self) -> &[&'static str] {
        #[cfg(feature = "error-trace")]
        { self.format_stack.as_slice() }
        #[cfg(not(feature = "error-trace"))]
        { &[] }
    }
}

impl ParseError {
    /// Creates an unexpected end-of-input error.
    pub fn unexpected_eof() -> (e: Self)
        ensures
            e.kind == ParseErrorKind::UnexpectedEof,
    {
        Self::new(ParseErrorKind::UnexpectedEof)
    }

    /// Creates an expecting end-of-input error.
    pub fn expecting_eof() -> Self {
        Self::new(ParseErrorKind::ExpectingEof)
    }

    /// Creates an invalid-tag error.
    pub fn invalid_tag() -> Self {
        Self::new(ParseErrorKind::InvalidTag)
    }

    /// Creates an invalid-choice error.
    pub fn invalid_choice() -> Self {
        Self::new(ParseErrorKind::InvalidChoice)
    }

    /// Creates an invalid-length error.
    pub fn invalid_length() -> Self {
        Self::new(ParseErrorKind::InvalidLength)
    }

    /// Creates a length-mismatch error.
    pub fn length_mismatch() -> Self {
        Self::new(ParseErrorKind::LengthMismatch)
    }

    /// Creates a predicate-failed error.
    pub fn predicate_failed() -> Self {
        Self::new(ParseErrorKind::PredicateFailed)
    }

    /// Creates a condition-rejected error.
    pub fn cond_rejected() -> Self {
        Self::new(ParseErrorKind::CondRejected)
    }

    /// Creates a non-canonical-encoding error.
    pub fn non_canonical() -> Self {
        Self::new(ParseErrorKind::NonCanonical)
    }

    /// Creates an overflow error.
    pub fn overflow() -> (e: Self)
        ensures
            e.kind == ParseErrorKind::Overflow,
    {
        Self::new(ParseErrorKind::Overflow)
    }

    /// Creates a recursion-limit-exceeded error.
    pub fn recursion_limit_exceeded() -> Self {
        Self::new(ParseErrorKind::RecursionLimitExceeded)
    }

    /// Creates a custom error with the given message.
    pub fn custom(msg: &'static str) -> Self {
        Self::new(ParseErrorKind::Custom(msg))
    }
}

} // verus!
impl fmt::Display for ParseError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let format_trace = self.format_trace();
        if !format_trace.is_empty() {
            write!(f, "{} while parsing format stack ", self.kind)?;
            for (i, format_name) in format_trace.iter().rev().enumerate() {
                if i > 0 {
                    f.write_str(" -> ")?;
                }
                write!(f, "`{}`", format_name)?;
            }
            Ok(())
        } else {
            match self.failed_format {
                Some(current_format) => {
                    write!(f, "{} while parsing format `{}`", self.kind, current_format)
                }
                None => write!(f, "{}", self.kind),
            }
        }
    }
}

#[cfg(feature = "std")]
impl std::error::Error for ParseError {}

verus! {

/// The coarse-grained kind of parse failure.
#[derive(Debug, Copy, PartialEq, Eq)]
pub enum ParseErrorKind {
    /// The parser needed more input bytes.
    UnexpectedEof,
    /// The parser expected the end of input, but more bytes were present.
    ExpectingEof,
    /// A tag or discriminant byte sequence was invalid.
    InvalidTag,
    /// Input did not match any branch of a choice-like format.
    InvalidChoice,
    /// A parsed length field was invalid.
    InvalidLength,
    /// A computed length disagreed with the enclosing format.
    LengthMismatch,
    /// A refinement predicate or semantic check failed.
    PredicateFailed,
    /// A branch was rejected after partial inspection.
    CondRejected,
    /// A non-canonical representation was observed.
    NonCanonical,
    /// An integer or size computation overflowed.
    Overflow,
    /// Recursion limit exceeded.
    RecursionLimitExceeded,
    /// A custom error message.
    Custom(&'static str),
}

impl Clone for ParseErrorKind {
    fn clone(&self) -> Self {
        *self
    }
}

} // verus!
impl fmt::Display for ParseErrorKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ParseErrorKind::UnexpectedEof => {
                f.write_str("input ended before the format could finish parsing")
            }
            ParseErrorKind::ExpectingEof => f.write_str("unexpected trailing input"),
            ParseErrorKind::InvalidTag => {
                f.write_str("tag or discriminant did not match the format")
            }
            ParseErrorKind::InvalidChoice => f.write_str("input did not match any choice branch"),
            ParseErrorKind::InvalidLength => {
                f.write_str("length field is outside the format's valid range")
            }
            ParseErrorKind::LengthMismatch => {
                f.write_str("a length-delimited parser did not consume the declared length")
            }
            ParseErrorKind::PredicateFailed => {
                f.write_str("parsed value failed a refinement predicate")
            }
            ParseErrorKind::CondRejected => f.write_str("conditional format rejected this branch"),
            ParseErrorKind::NonCanonical => f.write_str("non-canonical encoding"),
            ParseErrorKind::Overflow => f.write_str("integer or size computation overflowed"),
            ParseErrorKind::RecursionLimitExceeded => f.write_str("recursion limit exceeded"),
            ParseErrorKind::Custom(s) => f.write_str(s),
        }
    }
}