Skip to main content

standout_render/style/
error.rs

1//! Style-related error types.
2//!
3//! This module contains errors for both style validation and stylesheet parsing.
4
5use std::path::PathBuf;
6
7/// Error returned when style validation fails.
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub enum StyleValidationError {
10    /// An alias references a style that doesn't exist
11    UnresolvedAlias { from: String, to: String },
12    /// A cycle was detected in alias resolution
13    CycleDetected { path: Vec<String> },
14}
15
16impl std::fmt::Display for StyleValidationError {
17    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18        match self {
19            StyleValidationError::UnresolvedAlias { from, to } => {
20                write!(f, "style '{}' aliases non-existent style '{}'", from, to)
21            }
22            StyleValidationError::CycleDetected { path } => {
23                write!(f, "cycle detected in style aliases: {}", path.join(" -> "))
24            }
25        }
26    }
27}
28
29impl std::error::Error for StyleValidationError {}
30
31/// Error type for stylesheet parsing failures.
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum StylesheetError {
34    /// YAML parse error.
35    Parse {
36        /// Optional source file path.
37        path: Option<PathBuf>,
38        /// Error message from the YAML parser.
39        message: String,
40    },
41
42    /// Invalid color format.
43    InvalidColor {
44        /// Style name where the error occurred.
45        style: String,
46        /// The invalid color value.
47        value: String,
48        /// Optional source file path.
49        path: Option<PathBuf>,
50    },
51
52    /// Unknown attribute in style definition.
53    UnknownAttribute {
54        /// Style name where the error occurred.
55        style: String,
56        /// The unknown attribute name.
57        attribute: String,
58        /// Optional source file path.
59        path: Option<PathBuf>,
60    },
61
62    /// Invalid shorthand syntax.
63    InvalidShorthand {
64        /// Style name where the error occurred.
65        style: String,
66        /// The invalid shorthand value.
67        value: String,
68        /// Optional source file path.
69        path: Option<PathBuf>,
70    },
71
72    /// Alias validation error (dangling reference or cycle).
73    AliasError {
74        /// The underlying validation error.
75        source: StyleValidationError,
76    },
77
78    /// Invalid style definition structure.
79    InvalidDefinition {
80        /// Style name where the error occurred.
81        style: String,
82        /// Description of what was wrong.
83        message: String,
84        /// Optional source file path.
85        path: Option<PathBuf>,
86    },
87
88    /// File loading error.
89    Load {
90        /// Error message from the file loader.
91        message: String,
92    },
93}
94
95impl std::fmt::Display for StylesheetError {
96    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97        match self {
98            StylesheetError::Parse { path, message } => {
99                if let Some(p) = path {
100                    write!(f, "Failed to parse stylesheet {}: {}", p.display(), message)
101                } else {
102                    write!(f, "Failed to parse stylesheet: {}", message)
103                }
104            }
105            StylesheetError::InvalidColor { style, value, path } => {
106                let location = path
107                    .as_ref()
108                    .map(|p| format!(" in {}", p.display()))
109                    .unwrap_or_default();
110                write!(
111                    f,
112                    "Invalid color '{}' for style '{}'{}",
113                    value, style, location
114                )
115            }
116            StylesheetError::UnknownAttribute {
117                style,
118                attribute,
119                path,
120            } => {
121                let location = path
122                    .as_ref()
123                    .map(|p| format!(" in {}", p.display()))
124                    .unwrap_or_default();
125                write!(
126                    f,
127                    "Unknown attribute '{}' in style '{}'{}",
128                    attribute, style, location
129                )
130            }
131            StylesheetError::InvalidShorthand { style, value, path } => {
132                let location = path
133                    .as_ref()
134                    .map(|p| format!(" in {}", p.display()))
135                    .unwrap_or_default();
136                write!(
137                    f,
138                    "Invalid shorthand '{}' for style '{}'{}",
139                    value, style, location
140                )
141            }
142            StylesheetError::AliasError { source } => {
143                write!(f, "Style alias error: {}", source)
144            }
145            StylesheetError::InvalidDefinition {
146                style,
147                message,
148                path,
149            } => {
150                let location = path
151                    .as_ref()
152                    .map(|p| format!(" in {}", p.display()))
153                    .unwrap_or_default();
154                write!(
155                    f,
156                    "Invalid definition for style '{}'{}: {}",
157                    style, location, message
158                )
159            }
160            StylesheetError::Load { message } => {
161                write!(f, "Failed to load stylesheet: {}", message)
162            }
163        }
164    }
165}
166
167impl std::error::Error for StylesheetError {
168    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
169        match self {
170            StylesheetError::AliasError { source } => Some(source),
171            _ => None,
172        }
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179
180    #[test]
181    fn test_unresolved_alias_error_display() {
182        let err = StyleValidationError::UnresolvedAlias {
183            from: "orphan".to_string(),
184            to: "missing".to_string(),
185        };
186        let msg = err.to_string();
187        assert!(msg.contains("orphan"));
188        assert!(msg.contains("missing"));
189    }
190
191    #[test]
192    fn test_cycle_detected_error_display() {
193        let err = StyleValidationError::CycleDetected {
194            path: vec!["a".to_string(), "b".to_string(), "a".to_string()],
195        };
196        let msg = err.to_string();
197        assert!(msg.contains("cycle"));
198        assert!(msg.contains("a -> b -> a"));
199    }
200}