ass_core/utils/errors/
core.rs

1//! Core error type for ASS-RS operations
2//!
3//! Provides the main `CoreError` enum that wraps all error types from different
4//! modules in the crate. Designed for easy error propagation and conversion.
5//!
6//! # Error Philosophy
7//!
8//! - Use `thiserror` for structured error handling (no `anyhow` bloat)
9//! - Provide detailed context for debugging and user feedback
10//! - Support error chains with source information
11//! - Include suggestions for common error scenarios
12//! - Maintain zero-cost error handling where possible
13
14use alloc::{format, string::String};
15use core::fmt;
16
17#[cfg(not(feature = "std"))]
18extern crate alloc;
19#[cfg(feature = "std")]
20use thiserror::Error;
21
22/// Main error type for ASS-RS core operations
23///
24/// Wraps all error types from different modules to provide a unified
25/// error handling interface. Can be converted from module-specific errors.
26#[cfg_attr(feature = "std", derive(Error))]
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub enum CoreError {
29    /// Parsing errors from parser module
30    Parse(crate::parser::ParseError),
31
32    /// Tokenization errors
33    Tokenization(String),
34
35    /// Analysis errors
36    Analysis(String),
37
38    /// Plugin system errors
39    Plugin(String),
40
41    /// Color format parsing errors
42    InvalidColor(String),
43
44    /// Numeric value parsing errors
45    InvalidNumeric(String),
46
47    /// Time format parsing errors
48    InvalidTime(String),
49
50    /// UTF-8 encoding errors
51    Utf8Error { position: usize, message: String },
52
53    /// File I/O errors
54    Io(String),
55
56    /// Memory allocation errors
57    OutOfMemory(String),
58
59    /// Configuration errors
60    Config(String),
61
62    /// Validation errors
63    Validation(String),
64
65    /// Feature not supported in current configuration
66    FeatureNotSupported {
67        feature: String,
68        required_feature: String,
69    },
70
71    /// Version compatibility errors
72    VersionIncompatible { message: String },
73
74    /// Resource limit exceeded
75    ResourceLimitExceeded {
76        resource: String,
77        current: usize,
78        limit: usize,
79    },
80
81    /// Security policy violation
82    SecurityViolation(String),
83
84    /// Internal consistency error (should not happen)
85    Internal(String),
86}
87
88impl CoreError {
89    /// Create parse error from message
90    pub fn parse<T: fmt::Display>(message: T) -> Self {
91        Self::Parse(crate::parser::ParseError::IoError {
92            message: format!("{message}"),
93        })
94    }
95
96    /// Create internal error (indicates a bug)
97    pub fn internal<T: fmt::Display>(message: T) -> Self {
98        Self::Internal(format!("{message}"))
99    }
100
101    /// Check if error is recoverable
102    #[must_use]
103    pub const fn is_recoverable(&self) -> bool {
104        match self {
105            Self::Parse(parse_err) => !matches!(
106                parse_err,
107                crate::parser::ParseError::OutOfMemory { .. }
108                    | crate::parser::ParseError::InputTooLarge { .. }
109                    | crate::parser::ParseError::InternalError { .. }
110            ),
111            Self::Tokenization(_)
112            | Self::InvalidColor(_)
113            | Self::InvalidNumeric(_)
114            | Self::InvalidTime(_)
115            | Self::Validation(_)
116            | Self::Analysis(_)
117            | Self::Plugin(_)
118            | Self::Utf8Error { .. }
119            | Self::Io(_)
120            | Self::Config(_)
121            | Self::FeatureNotSupported { .. }
122            | Self::VersionIncompatible { .. } => true,
123
124            Self::OutOfMemory(_)
125            | Self::ResourceLimitExceeded { .. }
126            | Self::SecurityViolation(_)
127            | Self::Internal(_) => false,
128        }
129    }
130
131    /// Check if error indicates a bug in the library
132    #[must_use]
133    pub const fn is_internal_bug(&self) -> bool {
134        matches!(self, Self::Internal(_))
135    }
136
137    /// Get the underlying parse error if this is a parse error
138    #[must_use]
139    pub const fn as_parse_error(&self) -> Option<&crate::parser::ParseError> {
140        match self {
141            Self::Parse(parse_err) => Some(parse_err),
142            _ => None,
143        }
144    }
145
146    /// Get line number for errors that have location information
147    #[must_use]
148    pub const fn line_number(&self) -> Option<usize> {
149        match self {
150            Self::Parse(
151                crate::parser::ParseError::ExpectedSectionHeader { line }
152                | crate::parser::ParseError::UnclosedSectionHeader { line }
153                | crate::parser::ParseError::UnknownSection { line, .. }
154                | crate::parser::ParseError::InvalidFieldFormat { line }
155                | crate::parser::ParseError::InvalidFormatLine { line, .. }
156                | crate::parser::ParseError::FieldCountMismatch { line, .. }
157                | crate::parser::ParseError::InvalidTimeFormat { line, .. }
158                | crate::parser::ParseError::InvalidColorFormat { line, .. }
159                | crate::parser::ParseError::InvalidNumericValue { line, .. }
160                | crate::parser::ParseError::InvalidStyleOverride { line, .. }
161                | crate::parser::ParseError::InvalidDrawingCommand { line, .. }
162                | crate::parser::ParseError::UuDecodeError { line, .. }
163                | crate::parser::ParseError::MaxNestingDepth { line, .. }
164                | crate::parser::ParseError::InternalError { line, .. },
165            ) => Some(*line),
166            Self::Utf8Error { position, .. } => Some(*position),
167            _ => None,
168        }
169    }
170
171    /// Check if this is a specific type of parse error
172    #[must_use]
173    pub fn is_parse_error_type(&self, error_type: &str) -> bool {
174        match self {
175            Self::Parse(parse_err) => matches!(
176                (error_type, parse_err),
177                (
178                    "section_header",
179                    crate::parser::ParseError::ExpectedSectionHeader { .. }
180                ) | (
181                    "unclosed_header",
182                    crate::parser::ParseError::UnclosedSectionHeader { .. }
183                ) | (
184                    "unknown_section",
185                    crate::parser::ParseError::UnknownSection { .. }
186                ) | (
187                    "field_format",
188                    crate::parser::ParseError::InvalidFieldFormat { .. }
189                        | crate::parser::ParseError::FieldCountMismatch { .. }
190                ) | (
191                    "time_format",
192                    crate::parser::ParseError::InvalidTimeFormat { .. }
193                ) | (
194                    "color_format",
195                    crate::parser::ParseError::InvalidColorFormat { .. }
196                ) | (
197                    "numeric_value",
198                    crate::parser::ParseError::InvalidNumericValue { .. }
199                ) | ("utf8", crate::parser::ParseError::Utf8Error { .. })
200            ),
201            _ => false,
202        }
203    }
204}
205
206/// Result type alias for convenience
207pub type Result<T> = core::result::Result<T, CoreError>;
208
209/// nostd compatible Display implementation
210#[cfg(not(feature = "std"))]
211impl fmt::Display for CoreError {
212    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213        match self {
214            Self::Parse(parse_err) => write!(f, "Parse error: {parse_err}"),
215            Self::Tokenization(msg) => write!(f, "Tokenization error: {msg}"),
216            Self::Analysis(msg) => write!(f, "Analysis error: {msg}"),
217            Self::Plugin(msg) => write!(f, "Plugin error: {msg}"),
218            Self::InvalidColor(msg) => write!(f, "Invalid color format: {msg}"),
219            Self::InvalidNumeric(msg) => write!(f, "Invalid numeric value: {msg}"),
220            Self::InvalidTime(msg) => write!(f, "Invalid time format: {msg}"),
221            Self::Utf8Error { position, message } => {
222                write!(f, "UTF-8 encoding error at position {position}: {message}")
223            }
224            Self::Io(msg) => write!(f, "I/O error: {msg}"),
225            Self::OutOfMemory(msg) => write!(f, "Memory allocation failed: {msg}"),
226            Self::Config(msg) => write!(f, "Configuration error: {msg}"),
227            Self::Validation(msg) => write!(f, "Validation error: {msg}"),
228            Self::FeatureNotSupported {
229                feature,
230                required_feature,
231            } => {
232                write!(
233                    f,
234                    "Feature not supported: {feature} (requires feature '{required_feature}')"
235                )
236            }
237            Self::VersionIncompatible { message } => {
238                write!(f, "Version incompatibility: {message}")
239            }
240            Self::ResourceLimitExceeded {
241                resource,
242                current,
243                limit,
244            } => {
245                write!(f, "Resource limit exceeded: {resource} ({current}/{limit})")
246            }
247            Self::SecurityViolation(msg) => write!(f, "Security policy violation: {msg}"),
248            Self::Internal(msg) => {
249                write!(f, "Internal error: {msg} (this is a bug, please report)")
250            }
251        }
252    }
253}
254/// nostd compatible Error implementation
255#[cfg(not(feature = "std"))]
256impl core::error::Error for CoreError {}
257/// std compatible Display implementation
258#[cfg(feature = "std")]
259impl fmt::Display for CoreError {
260    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
261        match self {
262            Self::Parse(parse_err) => write!(f, "Parse error: {parse_err}"),
263            Self::Tokenization(msg) => write!(f, "Tokenization error: {msg}"),
264            Self::Analysis(msg) => write!(f, "Analysis error: {msg}"),
265            Self::Plugin(msg) => write!(f, "Plugin error: {msg}"),
266            Self::InvalidColor(msg) => write!(f, "Invalid color format: {msg}"),
267            Self::InvalidNumeric(msg) => write!(f, "Invalid numeric value: {msg}"),
268            Self::InvalidTime(msg) => write!(f, "Invalid time format: {msg}"),
269            Self::Utf8Error { position, message } => {
270                write!(f, "UTF-8 encoding error at position {position}: {message}")
271            }
272            Self::Io(msg) => write!(f, "I/O error: {msg}"),
273            Self::OutOfMemory(msg) => write!(f, "Memory allocation failed: {msg}"),
274            Self::Config(msg) => write!(f, "Configuration error: {msg}"),
275            Self::Validation(msg) => write!(f, "Validation error: {msg}"),
276            Self::FeatureNotSupported {
277                feature,
278                required_feature,
279            } => {
280                write!(
281                    f,
282                    "Feature not supported: {feature} (requires feature '{required_feature}')"
283                )
284            }
285            Self::VersionIncompatible { message } => {
286                write!(f, "Version incompatibility: {message}")
287            }
288            Self::ResourceLimitExceeded {
289                resource,
290                current,
291                limit,
292            } => {
293                write!(f, "Resource limit exceeded: {resource} ({current}/{limit})")
294            }
295            Self::SecurityViolation(msg) => write!(f, "Security policy violation: {msg}"),
296            Self::Internal(msg) => {
297                write!(f, "Internal error: {msg} (this is a bug, please report)")
298            }
299        }
300    }
301}
302
303#[cfg(test)]
304mod tests {
305    use super::*;
306
307    #[test]
308    fn error_creation() {
309        let parse_err = CoreError::parse("test message");
310        assert!(matches!(parse_err, CoreError::Parse(_)));
311    }
312
313    #[test]
314    fn internal_error() {
315        let internal_err = CoreError::internal("something went wrong");
316        assert!(matches!(internal_err, CoreError::Internal(_)));
317        assert!(internal_err.is_internal_bug());
318        assert!(!internal_err.is_recoverable());
319    }
320
321    #[test]
322    fn error_recoverability() {
323        assert!(CoreError::parse("test").is_recoverable());
324        assert!(!CoreError::internal("test").is_recoverable());
325    }
326
327    #[test]
328    fn internal_bug_detection() {
329        assert!(CoreError::internal("test").is_internal_bug());
330        assert!(!CoreError::parse("test").is_internal_bug());
331    }
332}