use alloc::{format, string::String, string::ToString, vec, vec::Vec};
use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum ValidationSeverity {
Info,
Warning,
Error,
}
impl fmt::Display for ValidationSeverity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Info => write!(f, "info"),
Self::Warning => write!(f, "warning"),
Self::Error => write!(f, "error"),
}
}
}
#[derive(Debug, Clone)]
pub struct StyleValidationIssue {
pub severity: ValidationSeverity,
pub message: String,
pub field: String,
pub suggestion: Option<String>,
}
#[derive(Debug, Clone)]
pub struct StyleInheritance<'a> {
pub name: &'a str,
pub parents: Vec<&'a str>,
pub children: Vec<&'a str>,
pub depth: usize,
pub has_circular_inheritance: bool,
}
#[derive(Debug, Clone)]
pub struct StyleConflict<'a> {
pub styles: Vec<&'a str>,
pub conflict_type: ConflictType,
pub description: String,
pub severity: ValidationSeverity,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConflictType {
DuplicateName,
CircularInheritance,
PropertyConflict,
MissingReference,
}
impl StyleValidationIssue {
#[must_use]
pub fn new(
severity: ValidationSeverity,
field: &str,
message: &str,
suggestion: Option<&str>,
) -> Self {
Self {
severity,
message: message.to_string(),
field: field.to_string(),
suggestion: suggestion.map(ToString::to_string),
}
}
#[must_use]
pub fn error(field: &str, message: &str) -> Self {
Self::new(ValidationSeverity::Error, field, message, None)
}
#[must_use]
pub fn warning(field: &str, message: &str) -> Self {
Self::new(ValidationSeverity::Warning, field, message, None)
}
#[must_use]
pub fn info_with_suggestion(field: &str, message: &str, suggestion: &str) -> Self {
Self::new(ValidationSeverity::Info, field, message, Some(suggestion))
}
}
impl<'a> StyleInheritance<'a> {
#[must_use]
pub const fn new(name: &'a str) -> Self {
Self {
name,
parents: Vec::new(),
children: Vec::new(),
depth: 0,
has_circular_inheritance: false,
}
}
pub fn add_parent(&mut self, parent: &'a str) {
if !self.parents.contains(&parent) {
self.parents.push(parent);
}
}
pub fn set_parent(&mut self, parent: &'a str) {
self.parents.clear();
self.parents.push(parent);
self.depth = 1; }
pub fn add_child(&mut self, child: &'a str) {
if !self.children.contains(&child) {
self.children.push(child);
}
}
#[must_use]
pub fn has_inheritance(&self) -> bool {
!self.parents.is_empty() || !self.children.is_empty()
}
#[must_use]
pub fn is_root(&self) -> bool {
self.parents.is_empty()
}
#[must_use]
pub fn is_leaf(&self) -> bool {
self.children.is_empty()
}
}
impl<'a> StyleConflict<'a> {
#[must_use]
pub fn new(
conflict_type: ConflictType,
styles: Vec<&'a str>,
description: &str,
severity: ValidationSeverity,
) -> Self {
Self {
styles,
conflict_type,
description: description.to_string(),
severity,
}
}
#[must_use]
pub fn duplicate_name(style_names: Vec<&'a str>) -> Self {
let description = format!("Duplicate style names found: {style_names:?}");
Self::new(
ConflictType::DuplicateName,
style_names,
&description,
ValidationSeverity::Error,
)
}
#[must_use]
pub fn circular_inheritance(cycle_styles: Vec<&'a str>) -> Self {
let description = format!("Circular inheritance detected: {cycle_styles:?}");
Self::new(
ConflictType::CircularInheritance,
cycle_styles,
&description,
ValidationSeverity::Error,
)
}
#[must_use]
pub fn missing_reference(referencing_style: &'a str, missing_style: &'a str) -> Self {
let description =
format!("Style '{referencing_style}' references non-existent style '{missing_style}'");
Self::new(
ConflictType::MissingReference,
vec![referencing_style],
&description,
ValidationSeverity::Error,
)
}
#[must_use]
pub fn missing_parent(style_name: &'a str, parent_name: &'a str) -> Self {
let description =
format!("Style '{style_name}' inherits from non-existent parent '{parent_name}'");
Self::new(
ConflictType::MissingReference,
vec![style_name],
&description,
ValidationSeverity::Warning,
)
}
}
impl fmt::Display for ConflictType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::DuplicateName => write!(f, "duplicate_name"),
Self::CircularInheritance => write!(f, "circular_inheritance"),
Self::PropertyConflict => write!(f, "property_conflict"),
Self::MissingReference => write!(f, "missing_reference"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validation_issue_creation() {
let issue = StyleValidationIssue::error("font_size", "Invalid font size value");
assert_eq!(issue.severity, ValidationSeverity::Error);
assert_eq!(issue.field, "font_size");
assert!(issue.suggestion.is_none());
let info = StyleValidationIssue::info_with_suggestion(
"font_name",
"Font may not be available",
"Use Arial as fallback",
);
assert_eq!(info.severity, ValidationSeverity::Info);
assert!(info.suggestion.is_some());
}
#[test]
fn inheritance_tracking() {
let mut inheritance = StyleInheritance::new("Child");
assert!(inheritance.is_root());
assert!(inheritance.is_leaf());
inheritance.add_parent("Parent");
assert!(!inheritance.is_root());
assert!(inheritance.has_inheritance());
inheritance.add_child("Grandchild");
assert!(!inheritance.is_leaf());
}
#[test]
fn conflict_creation() {
let conflict = StyleConflict::duplicate_name(vec!["Style1", "Style1"]);
assert_eq!(conflict.conflict_type, ConflictType::DuplicateName);
assert_eq!(conflict.severity, ValidationSeverity::Error);
let missing = StyleConflict::missing_reference("Child", "MissingParent");
assert_eq!(missing.conflict_type, ConflictType::MissingReference);
assert!(missing.description.contains("MissingParent"));
}
#[test]
fn severity_ordering() {
assert!(ValidationSeverity::Error > ValidationSeverity::Warning);
assert!(ValidationSeverity::Warning > ValidationSeverity::Info);
}
}