ass_core/utils/errors/
format.rs

1//! Format validation error utilities for ASS-RS
2//!
3//! Provides specialized error creation and validation functions for common
4//! ASS format types including colors, numeric values, and time formats.
5//! Focuses on providing detailed error context for user feedback.
6
7use super::CoreError;
8
9use core::fmt;
10
11#[cfg(not(feature = "std"))]
12extern crate alloc;
13#[cfg(not(feature = "std"))]
14use alloc::format;
15
16/// Create color format error with detailed context
17///
18/// Generates a `CoreError::InvalidColor` with descriptive message about
19/// the invalid color format encountered.
20///
21/// # Arguments
22///
23/// * `format` - The invalid color format string
24///
25/// # Examples
26///
27/// ```rust
28/// use ass_core::utils::errors::{invalid_color, CoreError};
29///
30/// let error = invalid_color("invalid_color");
31/// assert!(matches!(error, CoreError::InvalidColor(_)));
32/// ```
33pub fn invalid_color<T: fmt::Display>(format: T) -> CoreError {
34    CoreError::InvalidColor(format!("{format}"))
35}
36
37/// Create numeric parsing error with value and reason
38///
39/// Generates a `CoreError::InvalidNumeric` with both the invalid value
40/// and the reason parsing failed for better debugging.
41///
42/// # Arguments
43///
44/// * `value` - The value that failed to parse
45/// * `reason` - Description of why parsing failed
46pub fn invalid_numeric<T: fmt::Display>(value: T, reason: &str) -> CoreError {
47    CoreError::InvalidNumeric(format!("'{value}': {reason}"))
48}
49
50/// Create time format error with time and reason
51///
52/// Generates a `CoreError::InvalidTime` with the invalid time string
53/// and explanation of the format issue.
54///
55/// # Arguments
56///
57/// * `time` - The invalid time format string
58/// * `reason` - Description of the format issue
59pub fn invalid_time<T: fmt::Display>(time: T, reason: &str) -> CoreError {
60    CoreError::InvalidTime(format!("'{time}': {reason}"))
61}
62
63/// Validate ASS color format
64///
65/// Checks if a string matches valid ASS color formats:
66/// - &HBBGGRR (hexadecimal with transparency)
67/// - &HBBGGRR& (alternate format)
68/// - Decimal color values
69///
70/// # Returns
71///
72/// `Ok(())` if valid, error with suggestion if invalid
73///
74/// # Errors
75///
76/// Returns an error if the color format is invalid or cannot be parsed.
77pub fn validate_color_format(color: &str) -> Result<(), CoreError> {
78    let trimmed = color.trim();
79
80    if trimmed.is_empty() {
81        return Err(invalid_color("Empty color value"));
82    }
83
84    // Check for hex format (&H...)
85    if trimmed.starts_with("&H") || trimmed.starts_with("&h") {
86        let hex_part = if trimmed.ends_with('&') {
87            &trimmed[2..trimmed.len() - 1]
88        } else {
89            &trimmed[2..]
90        };
91
92        if hex_part.len() != 6 && hex_part.len() != 8 {
93            return Err(invalid_color(format!(
94                "Hex color '{trimmed}' must be 6 or 8 characters after &H"
95            )));
96        }
97
98        if !hex_part.chars().all(|c| c.is_ascii_hexdigit()) {
99            return Err(invalid_color(format!(
100                "Invalid hex digits in color '{trimmed}'"
101            )));
102        }
103    } else {
104        // Try parsing as decimal
105        if trimmed.parse::<u32>().is_err() {
106            return Err(invalid_color(format!(
107                "Color '{trimmed}' is neither valid hex (&HBBGGRR) nor decimal"
108            )));
109        }
110    }
111
112    Ok(())
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn color_error_creation() {
121        let error = invalid_color("invalid");
122        assert!(matches!(error, CoreError::InvalidColor(_)));
123    }
124
125    #[test]
126    fn numeric_error_creation() {
127        let error = invalid_numeric("abc", "not a number");
128        assert!(matches!(error, CoreError::InvalidNumeric(_)));
129    }
130
131    #[test]
132    fn time_error_creation() {
133        let error = invalid_time("invalid", "wrong format");
134        assert!(matches!(error, CoreError::InvalidTime(_)));
135    }
136
137    #[test]
138    fn validate_hex_color() {
139        assert!(validate_color_format("&H00FF00FF").is_ok());
140        assert!(validate_color_format("&H00FF00FF&").is_ok());
141        assert!(validate_color_format("&h00ff00ff").is_ok());
142        assert!(validate_color_format("&HFFFFFF").is_ok());
143    }
144
145    #[test]
146    fn validate_decimal_color() {
147        assert!(validate_color_format("16777215").is_ok()); // White
148        assert!(validate_color_format("0").is_ok()); // Black
149    }
150
151    #[test]
152    fn invalid_hex_color() {
153        assert!(validate_color_format("&HGG0000").is_err());
154        assert!(validate_color_format("&H123").is_err()); // Too short
155        assert!(validate_color_format("&H123456789").is_err()); // Too long
156    }
157
158    #[test]
159    fn invalid_color_format() {
160        assert!(validate_color_format("").is_err());
161        assert!(validate_color_format("invalid").is_err());
162        assert!(validate_color_format("#FF0000").is_err()); // Wrong prefix
163    }
164}