Skip to main content

devela/text/parse/
error.rs

1// devela::text::parse::error
2//
3//! Defines [`TextParseError`], [`TextParseErrorKind`].
4//
5
6use crate::{_impl_init, InvalidUtf8, TextCursor, impl_trait};
7
8#[doc = crate::_tags!(text parser error)]
9/// The category of a text parsing failure.
10#[doc = crate::_doc_meta!{location("text/parse")}]
11#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
12#[non_exhaustive]
13pub enum TextParseErrorKind {
14    /* expectation and boundary failures */
15    /// Reached end of input unexpectedly.
16    UnexpectedEof,
17    /// Expected a specific byte but found a different byte, or EOF.
18    UnexpectedByte {
19        /// The byte that was expected.
20        expected: u8,
21        /// The byte that was found, or `None` if EOF was reached.
22        found: Option<u8>,
23    },
24    /* decoding and lexical-value failures */
25    /// Decoded bytes were not valid UTF-8.
26    InvalidUtf8(InvalidUtf8),
27    /// Expected an ASCII digit but found another byte.
28    InvalidDigit,
29    /// Found an unsupported or malformed escape sequence.
30    InvalidEscape,
31
32    /* capacity and numeric-conversion failures */
33    /// The destination buffer could not hold the parsed output.
34    BufferTooSmall,
35    /// The parsed value overflowed the target representation.
36    Overflow,
37
38    /* quoted-text structural failures */
39    /// A quoted segment reached EOF before its closing quote.
40    UnterminatedQuote,
41    /// Found unexpected trailing data after a closing quote.
42    UnexpectedAfterQuote,
43}
44
45_impl_init![Self::UnexpectedEof => TextParseErrorKind];
46impl_trait![fmt::Display for TextParseErrorKind |self, f| {
47    use TextParseErrorKind as K;
48    match self {
49        K::UnexpectedEof => f.write_str("unexpected EOF"),
50        K::UnexpectedByte { expected, found } => match found {
51            Some(found) => write!(f, "unexpected byte: found {found:?}, expected {expected:?}"),
52            None => write!(f, "unexpected EOF: expected byte {expected:?}"),
53        },
54        K::InvalidUtf8(err) => write!(f, "invalid UTF-8 ({err})"),
55        K::InvalidDigit => f.write_str("invalid digit"),
56        K::BufferTooSmall => f.write_str("buffer too small"),
57        K::Overflow => f.write_str("overflow"),
58        //
59        K::UnterminatedQuote => f.write_str("unterminated quoted string"),
60        K::UnexpectedAfterQuote => f.write_str("unexpected data after closing quote"),
61        K::InvalidEscape => f.write_str("invalid escape"),
62    }
63}];
64
65#[doc = crate::_tags!(text parser error)]
66/// A text parsing failure paired with its cursor location.
67#[doc = crate::_doc_meta!{location("text/parse")}]
68#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
69pub struct TextParseError {
70    /// Cursor at which the failure was detected.
71    pub at: TextCursor,
72    /// Failure category and payload.
73    pub kind: TextParseErrorKind,
74}
75
76_impl_init![Self { at: TextCursor::INIT, kind: TextParseErrorKind::INIT } => TextParseError];
77impl_trait![fmt::Display+Error for TextParseError |self, f|
78    write!(f, "text parse error at: {:?}, {}", self.at.index, self.kind)
79];
80
81impl TextParseError {
82    /// Creates a parse error from its cursor and kind.
83    pub const fn new(at: TextCursor, kind: TextParseErrorKind) -> Self {
84        Self { at, kind }
85    }
86
87    /* expectation and boundary failures */
88
89    /// Returns an unexpected-EOF error.
90    pub const fn unexpected_eof(at: TextCursor) -> Self {
91        Self::new(at, TextParseErrorKind::UnexpectedEof)
92    }
93    /// Returns an unexpected-byte error.
94    pub const fn unexpected_byte(at: TextCursor, expected: u8, found: Option<u8>) -> Self {
95        Self::new(at, TextParseErrorKind::UnexpectedByte { expected, found })
96    }
97
98    /* decoding and lexical-value failures */
99
100    /// Returns an invalid-UTF-8 error.
101    ///
102    /// `at` marks the start of the decoded payload.
103    /// `err.valid_up_to` is relative to that decoded payload.
104    pub const fn invalid_utf8(at: TextCursor, err: InvalidUtf8) -> Self {
105        Self::new(at, TextParseErrorKind::InvalidUtf8(err))
106    }
107    /// Returns an invalid-digit error.
108    pub const fn invalid_digit(at: TextCursor) -> Self {
109        Self::new(at, TextParseErrorKind::InvalidDigit)
110    }
111    /// Returns an invalid-escape error.
112    pub const fn invalid_escape(at: TextCursor) -> Self {
113        Self::new(at, TextParseErrorKind::InvalidEscape)
114    }
115
116    /* capacity and numeric-conversion failures */
117
118    /// Returns a buffer-too-small error.
119    pub const fn buffer_too_small(at: TextCursor) -> Self {
120        Self::new(at, TextParseErrorKind::BufferTooSmall)
121    }
122    /// Returns an overflow error.
123    pub const fn overflow(at: TextCursor) -> Self {
124        Self::new(at, TextParseErrorKind::Overflow)
125    }
126
127    /* quoted-text structural failures */
128
129    /// Returns an unterminated-quote error.
130    pub const fn unterminated_quote(at: TextCursor) -> Self {
131        Self::new(at, TextParseErrorKind::UnterminatedQuote)
132    }
133    /// Returns an unexpected-after-quote error.
134    pub const fn unexpected_after_quote(at: TextCursor) -> Self {
135        Self::new(at, TextParseErrorKind::UnexpectedAfterQuote)
136    }
137}