ass_core/analysis/styles/
validation.rs1use alloc::{format, string::String, string::ToString, vec, vec::Vec};
22use core::fmt;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
26pub enum ValidationSeverity {
27 Info,
29 Warning,
31 Error,
33}
34
35impl fmt::Display for ValidationSeverity {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 match self {
38 Self::Info => write!(f, "info"),
39 Self::Warning => write!(f, "warning"),
40 Self::Error => write!(f, "error"),
41 }
42 }
43}
44
45#[derive(Debug, Clone)]
47pub struct StyleValidationIssue {
48 pub severity: ValidationSeverity,
50 pub message: String,
52 pub field: String,
54 pub suggestion: Option<String>,
56}
57
58#[derive(Debug, Clone)]
60pub struct StyleInheritance<'a> {
61 pub name: &'a str,
63 pub parents: Vec<&'a str>,
65 pub children: Vec<&'a str>,
67 pub depth: usize,
69 pub has_circular_inheritance: bool,
71}
72
73#[derive(Debug, Clone)]
75pub struct StyleConflict<'a> {
76 pub styles: Vec<&'a str>,
78 pub conflict_type: ConflictType,
80 pub description: String,
82 pub severity: ValidationSeverity,
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88pub enum ConflictType {
89 DuplicateName,
91 CircularInheritance,
93 PropertyConflict,
95 MissingReference,
97}
98
99impl StyleValidationIssue {
100 #[must_use]
102 pub fn new(
103 severity: ValidationSeverity,
104 field: &str,
105 message: &str,
106 suggestion: Option<&str>,
107 ) -> Self {
108 Self {
109 severity,
110 message: message.to_string(),
111 field: field.to_string(),
112 suggestion: suggestion.map(ToString::to_string),
113 }
114 }
115
116 #[must_use]
118 pub fn error(field: &str, message: &str) -> Self {
119 Self::new(ValidationSeverity::Error, field, message, None)
120 }
121
122 #[must_use]
124 pub fn warning(field: &str, message: &str) -> Self {
125 Self::new(ValidationSeverity::Warning, field, message, None)
126 }
127
128 #[must_use]
130 pub fn info_with_suggestion(field: &str, message: &str, suggestion: &str) -> Self {
131 Self::new(ValidationSeverity::Info, field, message, Some(suggestion))
132 }
133}
134
135impl<'a> StyleInheritance<'a> {
136 #[must_use]
138 pub const fn new(name: &'a str) -> Self {
139 Self {
140 name,
141 parents: Vec::new(),
142 children: Vec::new(),
143 depth: 0,
144 has_circular_inheritance: false,
145 }
146 }
147
148 pub fn add_parent(&mut self, parent: &'a str) {
150 if !self.parents.contains(&parent) {
151 self.parents.push(parent);
152 }
153 }
154
155 pub fn set_parent(&mut self, parent: &'a str) {
157 self.parents.clear();
158 self.parents.push(parent);
159 self.depth = 1; }
161
162 pub fn add_child(&mut self, child: &'a str) {
164 if !self.children.contains(&child) {
165 self.children.push(child);
166 }
167 }
168
169 #[must_use]
171 pub fn has_inheritance(&self) -> bool {
172 !self.parents.is_empty() || !self.children.is_empty()
173 }
174
175 #[must_use]
177 pub fn is_root(&self) -> bool {
178 self.parents.is_empty()
179 }
180
181 #[must_use]
183 pub fn is_leaf(&self) -> bool {
184 self.children.is_empty()
185 }
186}
187
188impl<'a> StyleConflict<'a> {
189 #[must_use]
191 pub fn new(
192 conflict_type: ConflictType,
193 styles: Vec<&'a str>,
194 description: &str,
195 severity: ValidationSeverity,
196 ) -> Self {
197 Self {
198 styles,
199 conflict_type,
200 description: description.to_string(),
201 severity,
202 }
203 }
204
205 #[must_use]
207 pub fn duplicate_name(style_names: Vec<&'a str>) -> Self {
208 let description = format!("Duplicate style names found: {style_names:?}");
209 Self::new(
210 ConflictType::DuplicateName,
211 style_names,
212 &description,
213 ValidationSeverity::Error,
214 )
215 }
216
217 #[must_use]
219 pub fn circular_inheritance(cycle_styles: Vec<&'a str>) -> Self {
220 let description = format!("Circular inheritance detected: {cycle_styles:?}");
221 Self::new(
222 ConflictType::CircularInheritance,
223 cycle_styles,
224 &description,
225 ValidationSeverity::Error,
226 )
227 }
228
229 #[must_use]
231 pub fn missing_reference(referencing_style: &'a str, missing_style: &'a str) -> Self {
232 let description =
233 format!("Style '{referencing_style}' references non-existent style '{missing_style}'");
234 Self::new(
235 ConflictType::MissingReference,
236 vec![referencing_style],
237 &description,
238 ValidationSeverity::Error,
239 )
240 }
241
242 #[must_use]
244 pub fn missing_parent(style_name: &'a str, parent_name: &'a str) -> Self {
245 let description =
246 format!("Style '{style_name}' inherits from non-existent parent '{parent_name}'");
247 Self::new(
248 ConflictType::MissingReference,
249 vec![style_name],
250 &description,
251 ValidationSeverity::Warning,
252 )
253 }
254}
255
256impl fmt::Display for ConflictType {
257 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
258 match self {
259 Self::DuplicateName => write!(f, "duplicate_name"),
260 Self::CircularInheritance => write!(f, "circular_inheritance"),
261 Self::PropertyConflict => write!(f, "property_conflict"),
262 Self::MissingReference => write!(f, "missing_reference"),
263 }
264 }
265}
266
267#[cfg(test)]
268mod tests {
269 use super::*;
270
271 #[test]
272 fn validation_issue_creation() {
273 let issue = StyleValidationIssue::error("font_size", "Invalid font size value");
274 assert_eq!(issue.severity, ValidationSeverity::Error);
275 assert_eq!(issue.field, "font_size");
276 assert!(issue.suggestion.is_none());
277
278 let info = StyleValidationIssue::info_with_suggestion(
279 "font_name",
280 "Font may not be available",
281 "Use Arial as fallback",
282 );
283 assert_eq!(info.severity, ValidationSeverity::Info);
284 assert!(info.suggestion.is_some());
285 }
286
287 #[test]
288 fn inheritance_tracking() {
289 let mut inheritance = StyleInheritance::new("Child");
290 assert!(inheritance.is_root());
291 assert!(inheritance.is_leaf());
292
293 inheritance.add_parent("Parent");
294 assert!(!inheritance.is_root());
295 assert!(inheritance.has_inheritance());
296
297 inheritance.add_child("Grandchild");
298 assert!(!inheritance.is_leaf());
299 }
300
301 #[test]
302 fn conflict_creation() {
303 let conflict = StyleConflict::duplicate_name(vec!["Style1", "Style1"]);
304 assert_eq!(conflict.conflict_type, ConflictType::DuplicateName);
305 assert_eq!(conflict.severity, ValidationSeverity::Error);
306
307 let missing = StyleConflict::missing_reference("Child", "MissingParent");
308 assert_eq!(missing.conflict_type, ConflictType::MissingReference);
309 assert!(missing.description.contains("MissingParent"));
310 }
311
312 #[test]
313 fn severity_ordering() {
314 assert!(ValidationSeverity::Error > ValidationSeverity::Warning);
315 assert!(ValidationSeverity::Warning > ValidationSeverity::Info);
316 }
317}