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}