Skip to main content

rich/
errors.rs

1//! Error types.
2//!
3//! Port of the exceptions in upstream `rich/errors.py`. Upstream's classes
4//! form a small hierarchy (`ConsoleError` and `StyleError` at the root); here
5//! each class is a [`RichError`] variant, and [`RichError::is_console_error`]
6//! and [`RichError::is_style_error`] answer upstream's `isinstance` checks.
7//! As more of the library is ported, additional variants are added here rather
8//! than scattering ad-hoc error types across modules.
9
10use std::fmt;
11
12/// Errors produced while parsing colors, styles, or markup.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum RichError {
15    /// A color could not be parsed (`rich.color.ColorParseError`).
16    ColorParse(String),
17    /// A style definition could not be parsed (`rich.errors.StyleSyntaxError`).
18    StyleSyntax(String),
19    /// Console markup was malformed (`rich.errors.MarkupError`).
20    Markup(String),
21    /// A JSON string could not be parsed (used by `rich.json.JSON`).
22    Json(String),
23    /// A regular expression could not be compiled or run. Python has no rich
24    /// equivalent — it lets `re.error` propagate — but a caller-supplied pattern
25    /// has to fail somewhere, and returning it beats panicking.
26    Regex(String),
27    /// The console's base theme cannot be popped (`rich.theme.ThemeStackError`).
28    ThemeStack(String),
29    /// A theme config file could not be read or parsed. Upstream surfaces
30    /// Python's `configparser` errors (`NoSectionError`, `DuplicateOptionError`,
31    /// `ParsingError`, `InterpolationSyntaxError`) and `OSError` here.
32    ThemeConfig(String),
33    /// A screen update without the alternate screen
34    /// (`rich.errors.NoAltScreen`).
35    NoAltScreen(String),
36    /// An error in console operation (`rich.errors.ConsoleError`).
37    Console(String),
38    /// An error in styles (`rich.errors.StyleError`).
39    Style(String),
40    /// No such style (`rich.errors.MissingStyle`).
41    MissingStyle(String),
42    /// The style stack is invalid (`rich.errors.StyleStackError`).
43    StyleStack(String),
44    /// An object is not renderable (`rich.errors.NotRenderableError`).
45    NotRenderable(String),
46    /// An error related to a live display (`rich.errors.LiveError`).
47    Live(String),
48}
49
50impl RichError {
51    /// The message upstream's exception carries, which is what `str(error)`
52    /// prints there. [`Display`](fmt::Display) prefixes it with the kind.
53    pub fn message(&self) -> &str {
54        match self {
55            RichError::ColorParse(message)
56            | RichError::StyleSyntax(message)
57            | RichError::Markup(message)
58            | RichError::Json(message)
59            | RichError::Regex(message)
60            | RichError::ThemeStack(message)
61            | RichError::ThemeConfig(message)
62            | RichError::NoAltScreen(message)
63            | RichError::Console(message)
64            | RichError::Style(message)
65            | RichError::MissingStyle(message)
66            | RichError::StyleStack(message)
67            | RichError::NotRenderable(message)
68            | RichError::Live(message) => message,
69        }
70    }
71
72    /// Whether upstream's class derives from `ConsoleError`: `ConsoleError`
73    /// itself, `StyleSyntaxError`, `StyleStackError`, `NotRenderableError`,
74    /// `MarkupError`, `LiveError` and `NoAltScreen`.
75    pub fn is_console_error(&self) -> bool {
76        matches!(
77            self,
78            RichError::Console(_)
79                | RichError::StyleSyntax(_)
80                | RichError::StyleStack(_)
81                | RichError::NotRenderable(_)
82                | RichError::Markup(_)
83                | RichError::Live(_)
84                | RichError::NoAltScreen(_)
85        )
86    }
87
88    /// Whether upstream's class derives from `StyleError`: `StyleError`
89    /// itself and `MissingStyle`.
90    pub fn is_style_error(&self) -> bool {
91        matches!(self, RichError::Style(_) | RichError::MissingStyle(_))
92    }
93}
94
95impl fmt::Display for RichError {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        match self {
98            RichError::ColorParse(msg) => write!(f, "color parse error: {msg}"),
99            RichError::StyleSyntax(msg) => write!(f, "style syntax error: {msg}"),
100            RichError::Markup(msg) => write!(f, "markup error: {msg}"),
101            RichError::Json(msg) => write!(f, "json parse error: {msg}"),
102            RichError::Regex(msg) => write!(f, "regex error: {msg}"),
103            RichError::ThemeStack(msg) => write!(f, "theme stack error: {msg}"),
104            RichError::ThemeConfig(msg) => write!(f, "theme config error: {msg}"),
105            RichError::NoAltScreen(msg) => write!(f, "{msg}"),
106            RichError::Console(msg) => write!(f, "console error: {msg}"),
107            RichError::Style(msg) => write!(f, "style error: {msg}"),
108            RichError::MissingStyle(msg) => write!(f, "missing style: {msg}"),
109            RichError::StyleStack(msg) => write!(f, "style stack error: {msg}"),
110            RichError::NotRenderable(msg) => write!(f, "not renderable: {msg}"),
111            RichError::Live(msg) => write!(f, "live error: {msg}"),
112        }
113    }
114}
115
116impl std::error::Error for RichError {}
117
118/// Convenience alias mirroring the subset of `rich` that raises on bad input.
119pub type Result<T> = std::result::Result<T, RichError>;
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    /// The hierarchy of `rich/errors.py`.
126    #[test]
127    fn hierarchy_matches_upstream() {
128        assert!(RichError::Markup(String::new()).is_console_error());
129        assert!(RichError::StyleSyntax(String::new()).is_console_error());
130        assert!(!RichError::StyleSyntax(String::new()).is_style_error());
131        assert!(RichError::MissingStyle(String::new()).is_style_error());
132        assert!(!RichError::MissingStyle(String::new()).is_console_error());
133        assert_eq!(RichError::Live("x".into()).message(), "x");
134    }
135}