Skip to main content

domtree/
error.rs

1//! Non-fatal parse diagnostics.
2//!
3//! Parsing never fails: [`crate::parse`] always returns a best-effort tree.
4//! Anything the parser had to recover from (a stray end tag, an unterminated
5//! comment, an unknown entity, …) is recorded as a [`ParseError`] and exposed
6//! via [`crate::Dom::errors`]. Linters and validators can inspect them; the
7//! common "just give me the tree" path can ignore them entirely.
8
9use core::fmt;
10
11/// A single recoverable problem encountered while parsing, with the byte
12/// offset into the source where it occurred.
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct ParseError {
15    /// Byte offset into the original source string.
16    pub position: usize,
17    /// What kind of problem this is.
18    pub kind: ErrorKind,
19}
20
21impl ParseError {
22    pub(crate) fn new(position: usize, kind: ErrorKind) -> Self {
23        ParseError { position, kind }
24    }
25
26    /// A short, human-readable description of the problem.
27    pub fn message(&self) -> &'static str {
28        self.kind.message()
29    }
30}
31
32/// The category of a [`ParseError`].
33///
34/// Marked `#[non_exhaustive]`: new recovery cases may be added without it being
35/// a breaking change, so always include a `_ =>` arm when matching.
36#[non_exhaustive]
37#[derive(Clone, Debug, PartialEq, Eq)]
38pub enum ErrorKind {
39    /// Input ended in the middle of a construct (tag, attribute, …).
40    UnexpectedEof,
41    /// A `<!-- comment` was never closed with `-->`.
42    UnterminatedComment,
43    /// A `<![CDATA[` section was never closed with `]]>`.
44    UnterminatedCdata,
45    /// A tag was opened with `<` but never closed with `>`.
46    UnterminatedTag,
47    /// An end tag (`</x>`) had no matching open element and was ignored.
48    StrayEndTag,
49    /// An end tag closed elements other than the current one (mis-nesting).
50    MismatchedEndTag,
51    /// An element was closed implicitly by another tag or end-of-input.
52    ImplicitlyClosed,
53    /// A `&entity;` reference was not recognised and was left verbatim.
54    UnknownEntity,
55    /// A `<! ... >` or `<? ... >` construct was treated as a bogus comment.
56    BogusComment,
57}
58
59impl ErrorKind {
60    pub(crate) fn message(&self) -> &'static str {
61        match self {
62            ErrorKind::UnexpectedEof => "unexpected end of input",
63            ErrorKind::UnterminatedComment => "unterminated comment",
64            ErrorKind::UnterminatedCdata => "unterminated CDATA section",
65            ErrorKind::UnterminatedTag => "unterminated tag",
66            ErrorKind::StrayEndTag => "stray end tag with no matching open element",
67            ErrorKind::MismatchedEndTag => "end tag closed mis-nested elements",
68            ErrorKind::ImplicitlyClosed => "element implicitly closed",
69            ErrorKind::UnknownEntity => "unknown character reference left verbatim",
70            ErrorKind::BogusComment => "bogus comment",
71        }
72    }
73}
74
75impl fmt::Display for ParseError {
76    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77        write!(f, "{} at byte {}", self.kind.message(), self.position)
78    }
79}
80
81#[cfg(feature = "std")]
82impl std::error::Error for ParseError {}