Skip to main content

styled_str/
errors.rs

1//! Error types.
2
3use core::{fmt, ops, str};
4
5use compile_fmt::{Ascii, compile_panic};
6
7/// Error parsing hexadecimal RGB color.
8#[derive(Debug)]
9#[non_exhaustive]
10pub enum HexColorError {
11    /// Color string doesn't start with a hash `#`.
12    NoHash,
13    /// Color string has unexpected length (not 4 or 7).
14    InvalidLen,
15    /// Color string contains an invalid hex digit.
16    InvalidHexDigit,
17}
18
19impl fmt::Display for HexColorError {
20    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
21        formatter.write_str(self.as_str())
22    }
23}
24
25impl HexColorError {
26    const fn as_str(&self) -> &'static str {
27        match self {
28            Self::NoHash => "color string doesn't start with a hash `#`",
29            Self::InvalidLen => "color string has unexpected length (not 4 or 7)",
30            Self::InvalidHexDigit => "color string contains an invalid hex digit",
31        }
32    }
33}
34
35#[cfg(feature = "std")]
36impl std::error::Error for HexColorError {}
37
38/// Kind of a [`ParseError`].
39#[derive(Debug)]
40#[non_exhaustive]
41pub enum ParseErrorKind {
42    /// Unfinished style, e.g. in `[[red`.
43    UnfinishedStyle,
44    /// Unsupported token in style, e.g. `[[what]]`.
45    UnsupportedStyle,
46    /// Error parsing a hexadecimal color spec, e.g. in `#c0g`.
47    HexColor(HexColorError),
48    /// Unfinished color specification, e.g. `color(`.
49    UnfinishedColor,
50    /// Invalid index color, e.g. `1234`.
51    InvalidIndexColor,
52    /// `on` token without the following color.
53    UnfinishedBackground,
54    /// Bogus delimiter encountered, e.g. in `[[red] on white]]`.
55    BogusDelimiter,
56    /// `*` token (copying previously used style) must be the first token in the spec.
57    NonInitialCopy,
58    /// `/` token (clearing style) must be the only token in the spec.
59    NonIsolatedClear,
60    /// Unsupported effect in a negation, e.g. `[[* -red]]`.
61    UnsupportedEffect,
62    /// Negation
63    NegationWithoutCopy,
64    /// Duplicate specified for the same property, like `[[bold bold]]` or `[[red green]]`.
65    DuplicateSpecifier,
66    /// Redundant negation, e.g. in `[[* -bold -bold]]`.
67    RedundantNegation,
68    /// ANSI escape char `\u{1b}` encountered in the text.
69    EscapeInText,
70
71    #[doc(hidden)] // should not occur unless private APIs are used
72    SpanOverflow,
73    #[doc(hidden)] // should not occur unless private APIs are used
74    TextOverflow,
75}
76
77impl ParseErrorKind {
78    pub(crate) const fn with_pos(self, pos: ops::Range<usize>) -> ParseError {
79        ParseError { kind: self, pos }
80    }
81
82    const fn as_str(&self) -> &'static str {
83        match self {
84            Self::UnfinishedStyle => "unfinished style definition",
85            Self::UnsupportedStyle => "unsupported style specifier",
86            Self::HexColor(err) => err.as_str(),
87            Self::UnfinishedColor => "unfinished color spec",
88            Self::InvalidIndexColor => "invalid indexed color",
89            Self::UnfinishedBackground => "no background specified after `on` keyword",
90            Self::BogusDelimiter => "bogus delimiter",
91            Self::NonInitialCopy => "* (copy) specifier must come first",
92            Self::NonIsolatedClear => "/ (clear) specifier must be the only token",
93            Self::UnsupportedEffect => "unsupported effect",
94            Self::NegationWithoutCopy => "negation without * (copy) specifier",
95            Self::DuplicateSpecifier => "duplicate specifier",
96            Self::RedundantNegation => "redundant negation",
97            Self::EscapeInText => "ANSI escape char 0x1b encountered in text",
98            Self::SpanOverflow => "too many spans",
99            Self::TextOverflow => "too much text",
100        }
101    }
102
103    const fn as_ascii_str(&self) -> Ascii<'static> {
104        Ascii::new(self.as_str())
105    }
106}
107
108impl fmt::Display for ParseErrorKind {
109    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
110        formatter.write_str(self.as_str())
111    }
112}
113
114/// Errors that can occur parsing [`StyledString`](crate::StyledString)s from the [rich syntax](crate#rich-syntax).
115#[derive(Debug)]
116pub struct ParseError {
117    kind: ParseErrorKind,
118    pos: ops::Range<usize>,
119}
120
121impl ParseError {
122    /// Returns the kind of this error.
123    pub const fn kind(&self) -> &ParseErrorKind {
124        &self.kind
125    }
126
127    /// Returns (byte) position in the source string that corresponds to this error.
128    pub fn pos(&self) -> ops::Range<usize> {
129        self.pos.clone()
130    }
131
132    #[track_caller]
133    pub(crate) const fn compile_panic(self, raw: &str) -> ! {
134        let (_, hl) = raw.as_bytes().split_at(self.pos.start);
135        let (hl, _) = hl.split_at(self.pos.end - self.pos.start);
136        let Ok(hl) = str::from_utf8(hl) else {
137            panic!("internal error: invalid error range");
138        };
139
140        compile_panic!(
141            "invalid styled string at ",
142            self.pos.start => compile_fmt::fmt::<usize>(), "..", self.pos.end => compile_fmt::fmt::<usize>(),
143            " ('", hl => compile_fmt::clip(64, "…"),
144            "'): ", self.kind.as_ascii_str() => compile_fmt::clip_ascii(42, "")
145        );
146    }
147}
148
149impl fmt::Display for ParseError {
150    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
151        write!(
152            formatter,
153            "invalid styled string at {:?}: {}",
154            self.pos, self.kind
155        )
156    }
157}
158
159#[cfg(feature = "std")]
160impl std::error::Error for ParseError {}