Skip to main content

ratatui_style/
error.rs

1//! Error type for the CSS engine.
2//!
3//! Hand-rolled (no `thiserror`) to keep the dependency surface minimal for an
4//! ecosystem crate.
5//!
6//! Every error carries an optional [`Loc`] (line:column, 1-based) pointing into
7//! the source text that produced it. Errors constructed during stylesheet
8//! parsing are tagged with a precise location; errors from value parsers that
9//! are called outside a parse context (e.g. ad-hoc `Color::parse`) leave
10//! `loc = None` and can be annotated with [`CssError::at`] by the caller.
11
12use std::fmt;
13
14/// A 1-based `line:column` position in a CSS source document.
15///
16/// `(0, 0)` (the default) represents an unknown location, e.g. for errors that
17/// were constructed without parse context. Compare with
18/// [`Loc::UNKNOWN`](Loc::UNKNOWN) or check [`Loc::is_unknown`].
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
20pub struct Loc {
21    pub line: u32,
22    pub column: u32,
23}
24
25impl Loc {
26    /// Sentinel for an unknown location: `(0, 0)`.
27    pub const UNKNOWN: Loc = Loc { line: 0, column: 0 };
28
29    /// Construct a known location.
30    pub const fn new(line: u32, column: u32) -> Self {
31        Self { line, column }
32    }
33
34    /// `true` when this is the [`UNKNOWN`](Loc::UNKNOWN) sentinel.
35    pub fn is_unknown(self) -> bool {
36        self.line == 0 && self.column == 0
37    }
38}
39
40impl fmt::Display for Loc {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        write!(f, "{}:{}", self.line, self.column)
43    }
44}
45
46/// The kind of [`CssError`], independent of where it occurred.
47#[derive(Debug, Clone, PartialEq, Eq)]
48pub enum CssErrorKind {
49    /// A color value could not be parsed (e.g. `"#zzz"`).
50    InvalidColor(String),
51    /// A selector could not be parsed (e.g. `"..::"`).
52    InvalidSelector(String),
53    /// A length/sizing value could not be parsed (e.g. `"width: banana"`).
54    InvalidLength(String),
55    /// A `var(--name)` referenced a variable that is not defined in any token table.
56    UndefinedVariable(String),
57    /// A `var()` reference chain is too deep or cyclic.
58    CircularVariable(String),
59    /// (Strict mode only.) A declaration used a property the engine does not know.
60    UnknownProperty(String),
61    /// An I/O error occurred (e.g. reading a stylesheet from disk).
62    Io(String),
63}
64
65impl fmt::Display for CssErrorKind {
66    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67        match self {
68            Self::InvalidColor(v) => write!(f, "invalid color: {v}"),
69            Self::InvalidSelector(v) => write!(f, "invalid selector: {v}"),
70            Self::InvalidLength(v) => write!(f, "invalid length: {v}"),
71            Self::UndefinedVariable(v) => write!(f, "undefined variable: {v}"),
72            Self::CircularVariable(v) => write!(f, "circular variable reference: {v}"),
73            Self::UnknownProperty(v) => write!(f, "unknown property: {v}"),
74            Self::Io(v) => write!(f, "io error: {v}"),
75        }
76    }
77}
78
79/// All errors produced by `ratatui-style`.
80///
81/// A `CssError` is a [`kind`](CssError::kind) plus an optional [`Loc`]. Use the
82/// `invalid_color` / `unknown_property` / … constructors for a `loc = None`
83/// error, then chain `.at(line, col)` (or `.with_loc(loc)`) to attach a
84/// position.
85#[derive(Debug, Clone, PartialEq, Eq)]
86pub struct CssError {
87    pub kind: CssErrorKind,
88    pub loc: Option<Loc>,
89}
90
91impl CssError {
92    // --- constructors (loc = None) ----------------------------------------
93
94    /// Construct from an explicit kind with no location.
95    pub fn from_kind(kind: CssErrorKind) -> Self {
96        Self { kind, loc: None }
97    }
98
99    pub fn invalid_color(msg: impl Into<String>) -> Self {
100        Self::from_kind(CssErrorKind::InvalidColor(msg.into()))
101    }
102    pub fn invalid_selector(msg: impl Into<String>) -> Self {
103        Self::from_kind(CssErrorKind::InvalidSelector(msg.into()))
104    }
105    pub fn invalid_length(msg: impl Into<String>) -> Self {
106        Self::from_kind(CssErrorKind::InvalidLength(msg.into()))
107    }
108    pub fn undefined_variable(msg: impl Into<String>) -> Self {
109        Self::from_kind(CssErrorKind::UndefinedVariable(msg.into()))
110    }
111    pub fn circular_variable(msg: impl Into<String>) -> Self {
112        Self::from_kind(CssErrorKind::CircularVariable(msg.into()))
113    }
114    /// (Strict mode only.) An unknown CSS property was used.
115    pub fn unknown_property(msg: impl Into<String>) -> Self {
116        Self::from_kind(CssErrorKind::UnknownProperty(msg.into()))
117    }
118    pub fn io(msg: impl Into<String>) -> Self {
119        Self::from_kind(CssErrorKind::Io(msg.into()))
120    }
121
122    // --- location builder -------------------------------------------------
123
124    /// Attach a 1-based `line:column` to this error, returning it for chaining.
125    pub fn at(mut self, line: u32, column: u32) -> Self {
126        self.loc = Some(Loc::new(line, column));
127        self
128    }
129
130    /// Attach an explicit [`Loc`].
131    pub fn with_loc(mut self, loc: Loc) -> Self {
132        self.loc = Some(loc);
133        self
134    }
135
136    /// A reference to the [`CssErrorKind`] this wraps.
137    pub fn kind(&self) -> &CssErrorKind {
138        &self.kind
139    }
140}
141
142impl fmt::Display for CssError {
143    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144        match self.loc {
145            Some(loc) if !loc.is_unknown() => write!(f, "{} at line {}:{}", self.kind, loc.line, loc.column),
146            _ => write!(f, "{}", self.kind),
147        }
148    }
149}
150
151impl std::error::Error for CssError {}
152
153pub type Result<T> = std::result::Result<T, CssError>;