ass_editor/core/
errors.rs

1//! Error types for the ass-editor crate
2//!
3//! Provides the main `EditorError` enum that wraps `CoreError` from ass-core
4//! and adds editor-specific error cases. Follows the same philosophy as core:
5//! - Use thiserror for structured error handling (no anyhow)
6//! - Provide detailed context for debugging
7//! - Support error chains with source information
8//! - Maintain zero-cost error handling where possible
9
10use ass_core::utils::errors::CoreError;
11use core::fmt;
12
13#[cfg(feature = "std")]
14use thiserror::Error;
15
16#[cfg(not(feature = "std"))]
17use alloc::string::{String, ToString};
18
19/// Main error type for ass-editor operations
20///
21/// Wraps `CoreError` from ass-core and adds editor-specific error cases
22/// for document management, command execution, and session handling.
23#[cfg_attr(feature = "std", derive(Error))]
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub enum EditorError {
26    /// Errors from ass-core
27    #[cfg_attr(feature = "std", error(transparent))]
28    Core(CoreError),
29
30    /// Document not found in session
31    #[cfg_attr(feature = "std", error("Document not found: {id}"))]
32    DocumentNotFound { id: String },
33
34    /// Invalid document position
35    #[cfg_attr(
36        feature = "std",
37        error("Invalid position: line {line}, column {column}")
38    )]
39    InvalidPosition { line: usize, column: usize },
40
41    /// Position out of bounds
42    #[cfg_attr(
43        feature = "std",
44        error("Position out of bounds: {position} (document length: {length})")
45    )]
46    PositionOutOfBounds { position: usize, length: usize },
47
48    /// Invalid text range
49    #[cfg_attr(
50        feature = "std",
51        error("Invalid range: start {start}, end {end} (document length: {length})")
52    )]
53    InvalidRange {
54        start: usize,
55        end: usize,
56        length: usize,
57    },
58
59    /// Command execution failed
60    #[cfg_attr(feature = "std", error("Command execution failed: {message}"))]
61    CommandFailed { message: String },
62
63    /// Undo/redo operation failed
64    #[cfg_attr(feature = "std", error("History operation failed: {message}"))]
65    HistoryError { message: String },
66
67    /// No operation to undo
68    #[cfg_attr(feature = "std", error("Nothing to undo"))]
69    NothingToUndo,
70
71    /// No operation to redo
72    #[cfg_attr(feature = "std", error("Nothing to redo"))]
73    NothingToRedo,
74
75    /// Session limit exceeded
76    #[cfg_attr(
77        feature = "std",
78        error("Session limit exceeded: {current}/{limit} sessions")
79    )]
80    SessionLimitExceeded { current: usize, limit: usize },
81
82    /// Search index error
83    #[cfg_attr(feature = "std", error("Search index error: {message}"))]
84    SearchIndexError { message: String },
85
86    /// Extension error
87    #[cfg_attr(feature = "std", error("Extension error: {extension}: {message}"))]
88    ExtensionError { extension: String, message: String },
89
90    /// Feature not available without specific feature flag
91    #[cfg_attr(
92        feature = "std",
93        error("Feature '{feature}' requires '{required_feature}' feature flag")
94    )]
95    FeatureNotEnabled {
96        feature: String,
97        required_feature: String,
98    },
99
100    /// Arena allocation error (when using arena feature)
101    #[cfg_attr(feature = "std", error("Arena allocation failed: {message}"))]
102    ArenaAllocationFailed { message: String },
103
104    /// Section not found in document
105    #[cfg_attr(feature = "std", error("Section not found: {section}"))]
106    SectionNotFound { section: String },
107
108    /// Rope operation failed (when using rope feature)
109    #[cfg_attr(feature = "std", error("Rope operation failed: {message}"))]
110    RopeOperationFailed { message: String },
111
112    /// Event channel error
113    #[cfg_attr(feature = "std", error("Event channel error: {message}"))]
114    EventChannelError { message: String },
115
116    /// Validation error from lazy validator
117    #[cfg_attr(feature = "std", error("Validation error: {message}"))]
118    ValidationError { message: String },
119
120    /// Import/export error
121    #[cfg_attr(feature = "std", error("IO error: {0}"))]
122    IoError(String),
123
124    /// Invalid format error
125    #[cfg_attr(feature = "std", error("Invalid format: {0}"))]
126    InvalidFormat(String),
127
128    /// Unsupported format error
129    #[cfg_attr(feature = "std", error("Unsupported format: {0}"))]
130    UnsupportedFormat(String),
131
132    /// Thread safety error (multi-thread feature)
133    #[cfg_attr(feature = "std", error("Thread safety error: {message}"))]
134    ThreadSafetyError { message: String },
135
136    /// Builder validation error
137    #[cfg_attr(feature = "std", error("Builder validation error: {message}"))]
138    BuilderValidationError { message: String },
139
140    /// Serialization error
141    #[cfg_attr(feature = "std", error("Serialization error: {message}"))]
142    SerializationError { message: String },
143
144    /// Format line parsing error
145    #[cfg_attr(feature = "std", error("Format line error: {message}"))]
146    FormatLineError { message: String },
147}
148
149impl EditorError {
150    /// Create a new command failed error
151    pub fn command_failed<T: fmt::Display>(message: T) -> Self {
152        Self::CommandFailed {
153            message: message.to_string(),
154        }
155    }
156
157    /// Create a new validation error
158    pub fn validation<T: fmt::Display>(message: T) -> Self {
159        Self::ValidationError {
160            message: message.to_string(),
161        }
162    }
163
164    /// Create a new IO error
165    pub fn io<T: fmt::Display>(message: T) -> Self {
166        Self::IoError(message.to_string())
167    }
168
169    /// Create a new builder validation error
170    pub fn builder_validation<T: fmt::Display>(message: T) -> Self {
171        Self::BuilderValidationError {
172            message: message.to_string(),
173        }
174    }
175
176    /// Create a new serialization error
177    pub fn serialization<T: fmt::Display>(message: T) -> Self {
178        Self::SerializationError {
179            message: message.to_string(),
180        }
181    }
182
183    /// Create a new format line error
184    pub fn format_line<T: fmt::Display>(message: T) -> Self {
185        Self::FormatLineError {
186            message: message.to_string(),
187        }
188    }
189
190    /// Check if error is recoverable
191    #[must_use]
192    pub const fn is_recoverable(&self) -> bool {
193        match self {
194            Self::Core(core_err) => core_err.is_recoverable(),
195            Self::DocumentNotFound { .. }
196            | Self::InvalidPosition { .. }
197            | Self::PositionOutOfBounds { .. }
198            | Self::InvalidRange { .. }
199            | Self::CommandFailed { .. }
200            | Self::HistoryError { .. }
201            | Self::NothingToUndo
202            | Self::NothingToRedo
203            | Self::SearchIndexError { .. }
204            | Self::ExtensionError { .. }
205            | Self::FeatureNotEnabled { .. }
206            | Self::RopeOperationFailed { .. }
207            | Self::EventChannelError { .. }
208            | Self::ValidationError { .. }
209            | Self::IoError(..)
210            | Self::InvalidFormat(..)
211            | Self::UnsupportedFormat(..)
212            | Self::BuilderValidationError { .. }
213            | Self::SerializationError { .. }
214            | Self::FormatLineError { .. }
215            | Self::SectionNotFound { .. } => true,
216            Self::SessionLimitExceeded { .. }
217            | Self::ArenaAllocationFailed { .. }
218            | Self::ThreadSafetyError { .. } => false,
219        }
220    }
221
222    /// Check if this is a position-related error
223    #[must_use]
224    pub const fn is_position_error(&self) -> bool {
225        matches!(
226            self,
227            Self::InvalidPosition { .. }
228                | Self::PositionOutOfBounds { .. }
229                | Self::InvalidRange { .. }
230        )
231    }
232
233    /// Check if this is a history-related error
234    #[must_use]
235    pub const fn is_history_error(&self) -> bool {
236        matches!(
237            self,
238            Self::HistoryError { .. } | Self::NothingToUndo | Self::NothingToRedo
239        )
240    }
241
242    /// Get the underlying core error if this wraps one
243    #[must_use]
244    pub const fn as_core_error(&self) -> Option<&CoreError> {
245        match self {
246            Self::Core(core_err) => Some(core_err),
247            _ => None,
248        }
249    }
250}
251
252/// Result type alias for editor operations
253pub type Result<T> = core::result::Result<T, EditorError>;
254
255/// Implement `From<CoreError>` for automatic conversion
256impl From<CoreError> for EditorError {
257    fn from(err: CoreError) -> Self {
258        Self::Core(err)
259    }
260}
261
262/// nostd compatible Display implementation
263#[cfg(not(feature = "std"))]
264impl fmt::Display for EditorError {
265    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
266        match self {
267            Self::Core(err) => write!(f, "{err}"),
268            Self::DocumentNotFound { id } => write!(f, "Document not found: {id}"),
269            Self::InvalidPosition { line, column } => {
270                write!(f, "Invalid position: line {line}, column {column}")
271            }
272            Self::PositionOutOfBounds { position, length } => {
273                write!(
274                    f,
275                    "Position out of bounds: {position} (document length: {length})"
276                )
277            }
278            Self::InvalidRange { start, end, length } => {
279                write!(
280                    f,
281                    "Invalid range: start {start}, end {end} (document length: {length})"
282                )
283            }
284            Self::CommandFailed { message } => write!(f, "Command execution failed: {message}"),
285            Self::HistoryError { message } => write!(f, "History operation failed: {message}"),
286            Self::NothingToUndo => write!(f, "Nothing to undo"),
287            Self::NothingToRedo => write!(f, "Nothing to redo"),
288            Self::SessionLimitExceeded { current, limit } => {
289                write!(f, "Session limit exceeded: {current}/{limit} sessions")
290            }
291            Self::SearchIndexError { message } => write!(f, "Search index error: {message}"),
292            Self::ExtensionError { extension, message } => {
293                write!(f, "Extension error: {extension}: {message}")
294            }
295            Self::FeatureNotEnabled {
296                feature,
297                required_feature,
298            } => {
299                write!(
300                    f,
301                    "Feature '{feature}' requires '{required_feature}' feature flag"
302                )
303            }
304            Self::ArenaAllocationFailed { message } => {
305                write!(f, "Arena allocation failed: {message}")
306            }
307            Self::RopeOperationFailed { message } => write!(f, "Rope operation failed: {message}"),
308            Self::EventChannelError { message } => write!(f, "Event channel error: {message}"),
309            Self::ValidationError { message } => write!(f, "Validation error: {message}"),
310            Self::IoError(message) => write!(f, "IO error: {message}"),
311            Self::InvalidFormat(message) => write!(f, "Invalid format: {message}"),
312            Self::UnsupportedFormat(format) => write!(f, "Unsupported format: {format}"),
313            Self::ThreadSafetyError { message } => write!(f, "Thread safety error: {message}"),
314            Self::BuilderValidationError { message } => {
315                write!(f, "Builder validation error: {message}")
316            }
317            Self::SerializationError { message } => write!(f, "Serialization error: {message}"),
318            Self::FormatLineError { message } => write!(f, "Format line error: {message}"),
319            Self::SectionNotFound { section } => write!(f, "Section not found: {section}"),
320        }
321    }
322}
323
324/// nostd compatible Error implementation
325#[cfg(not(feature = "std"))]
326impl core::error::Error for EditorError {}
327
328#[cfg(test)]
329mod tests {
330    use super::*;
331    #[cfg(not(feature = "std"))]
332    use alloc::string::ToString;
333
334    #[test]
335    fn error_conversion_from_core() {
336        let core_err = CoreError::parse("test error");
337        let editor_err: EditorError = core_err.into();
338        assert!(matches!(editor_err, EditorError::Core(_)));
339    }
340
341    #[test]
342    fn error_recoverability() {
343        assert!(EditorError::command_failed("test").is_recoverable());
344        assert!(EditorError::validation("test").is_recoverable());
345        assert!(!EditorError::SessionLimitExceeded {
346            current: 10,
347            limit: 10
348        }
349        .is_recoverable());
350    }
351
352    #[test]
353    fn position_error_detection() {
354        assert!(EditorError::InvalidPosition { line: 1, column: 1 }.is_position_error());
355        assert!(EditorError::PositionOutOfBounds {
356            position: 100,
357            length: 50
358        }
359        .is_position_error());
360        assert!(!EditorError::command_failed("test").is_position_error());
361    }
362
363    #[test]
364    fn history_error_detection() {
365        assert!(EditorError::NothingToUndo.is_history_error());
366        assert!(EditorError::NothingToRedo.is_history_error());
367        assert!(EditorError::HistoryError {
368            message: "test".to_string()
369        }
370        .is_history_error());
371        assert!(!EditorError::command_failed("test").is_history_error());
372    }
373
374    #[test]
375    fn core_error_extraction() {
376        let core_err = CoreError::parse("test");
377        let editor_err = EditorError::Core(core_err.clone());
378        assert_eq!(editor_err.as_core_error(), Some(&core_err));
379        assert_eq!(EditorError::command_failed("test").as_core_error(), None);
380    }
381
382    #[test]
383    fn new_error_types() {
384        // Test builder validation error
385        let builder_err = EditorError::builder_validation("Invalid field value");
386        assert!(builder_err.is_recoverable());
387        assert!(matches!(
388            builder_err,
389            EditorError::BuilderValidationError { .. }
390        ));
391
392        // Test serialization error
393        let serialization_err = EditorError::serialization("Failed to serialize AST");
394        assert!(serialization_err.is_recoverable());
395        assert!(matches!(
396            serialization_err,
397            EditorError::SerializationError { .. }
398        ));
399
400        // Test format line error
401        let format_err = EditorError::format_line("Invalid format specification");
402        assert!(format_err.is_recoverable());
403        assert!(matches!(format_err, EditorError::FormatLineError { .. }));
404    }
405
406    #[test]
407    fn error_display_new_types() {
408        let builder_err = EditorError::builder_validation("test");
409        assert_eq!(builder_err.to_string(), "Builder validation error: test");
410
411        let serialization_err = EditorError::serialization("test");
412        assert_eq!(serialization_err.to_string(), "Serialization error: test");
413
414        let format_err = EditorError::format_line("test");
415        assert_eq!(format_err.to_string(), "Format line error: test");
416    }
417}