miyabi_modes/
error.rs

1use miyabi_types::error::{ErrorCode, UnifiedError};
2use std::any::Any;
3use thiserror::Error;
4
5#[derive(Debug, Error)]
6pub enum ModeError {
7    #[error("Mode not found: {0}")]
8    ModeNotFound(String),
9
10    #[error("Duplicate mode slug: {0}")]
11    DuplicateSlug(String),
12
13    #[error("Invalid mode definition: {0}")]
14    InvalidDefinition(String),
15
16    #[error("YAML parsing error: {0}")]
17    YamlError(#[from] serde_yaml::Error),
18
19    #[error("IO error: {0}")]
20    IoError(#[from] std::io::Error),
21
22    #[error("Invalid file regex: {0}")]
23    InvalidRegex(#[from] regex::Error),
24
25    #[error("Mode validation failed: {0}")]
26    ValidationFailed(String),
27
28    #[error("Missing required field: {0}")]
29    MissingField(String),
30}
31
32pub type ModeResult<T> = Result<T, ModeError>;
33
34// ============================================================================
35// UnifiedError Implementation
36// ============================================================================
37
38impl UnifiedError for ModeError {
39    fn code(&self) -> ErrorCode {
40        match self {
41            Self::ModeNotFound(_) => ErrorCode::FILE_NOT_FOUND,
42            Self::DuplicateSlug(_) => ErrorCode::VALIDATION_ERROR,
43            Self::InvalidDefinition(_) => ErrorCode::INVALID_CONFIG,
44            Self::YamlError(_) => ErrorCode::PARSE_ERROR,
45            Self::IoError(e) => match e.kind() {
46                std::io::ErrorKind::NotFound => ErrorCode::FILE_NOT_FOUND,
47                std::io::ErrorKind::PermissionDenied => ErrorCode::PERMISSION_DENIED,
48                _ => ErrorCode::IO_ERROR,
49            },
50            Self::InvalidRegex(_) => ErrorCode::REGEX_ERROR,
51            Self::ValidationFailed(_) => ErrorCode::VALIDATION_ERROR,
52            Self::MissingField(_) => ErrorCode::MISSING_CONFIG,
53        }
54    }
55
56    fn user_message(&self) -> String {
57        match self {
58            Self::ModeNotFound(name) => format!(
59                "Mode '{}' was not found. Please check the mode name and ensure it has been registered.",
60                name
61            ),
62            Self::DuplicateSlug(slug) => format!(
63                "A mode with slug '{}' already exists. Please use a different slug for your mode.",
64                slug
65            ),
66            Self::InvalidDefinition(msg) => format!(
67                "Invalid mode definition: {}. Please check your mode configuration.",
68                msg
69            ),
70            Self::YamlError(e) => format!(
71                "Failed to parse YAML configuration: {}. Please check your YAML syntax.",
72                e
73            ),
74            Self::InvalidRegex(e) => format!(
75                "Invalid file pattern regex: {}. Please check your regex syntax.",
76                e
77            ),
78            Self::ValidationFailed(msg) => format!(
79                "Mode validation failed: {}. Please review your mode configuration.",
80                msg
81            ),
82            Self::MissingField(field) => format!(
83                "Required field '{}' is missing from mode configuration. Please add this field.",
84                field
85            ),
86            // Reuse existing thiserror messages for other variants
87            _ => self.to_string(),
88        }
89    }
90
91    fn context(&self) -> Option<&dyn Any> {
92        match self {
93            Self::ModeNotFound(name) => Some(name as &dyn Any),
94            Self::DuplicateSlug(slug) => Some(slug as &dyn Any),
95            Self::InvalidDefinition(msg) => Some(msg as &dyn Any),
96            Self::ValidationFailed(msg) => Some(msg as &dyn Any),
97            Self::MissingField(field) => Some(field as &dyn Any),
98            _ => None,
99        }
100    }
101}
102
103#[cfg(test)]
104mod unified_error_tests {
105    use super::*;
106
107    #[test]
108    fn test_mode_error_codes() {
109        let error = ModeError::ModeNotFound("test".to_string());
110        assert_eq!(error.code(), ErrorCode::FILE_NOT_FOUND);
111
112        let error = ModeError::DuplicateSlug("test".to_string());
113        assert_eq!(error.code(), ErrorCode::VALIDATION_ERROR);
114
115        let error = ModeError::InvalidDefinition("test".to_string());
116        assert_eq!(error.code(), ErrorCode::INVALID_CONFIG);
117
118        let error = ModeError::ValidationFailed("test".to_string());
119        assert_eq!(error.code(), ErrorCode::VALIDATION_ERROR);
120
121        let error = ModeError::MissingField("name".to_string());
122        assert_eq!(error.code(), ErrorCode::MISSING_CONFIG);
123    }
124
125    #[test]
126    fn test_user_messages() {
127        let error = ModeError::ModeNotFound("custom-mode".to_string());
128        let msg = error.user_message();
129        assert!(msg.contains("custom-mode"));
130        assert!(msg.contains("not found"));
131
132        let error = ModeError::DuplicateSlug("my-mode".to_string());
133        let msg = error.user_message();
134        assert!(msg.contains("my-mode"));
135        assert!(msg.contains("already exists"));
136
137        let error = ModeError::MissingField("description".to_string());
138        let msg = error.user_message();
139        assert!(msg.contains("description"));
140        assert!(msg.contains("missing"));
141    }
142
143    #[test]
144    fn test_context_extraction() {
145        let error = ModeError::ModeNotFound("test".to_string());
146        assert!(error.context().is_some());
147
148        let error = ModeError::DuplicateSlug("test".to_string());
149        assert!(error.context().is_some());
150
151        let error = ModeError::MissingField("name".to_string());
152        assert!(error.context().is_some());
153
154        let error = ModeError::IoError(std::io::Error::other("test"));
155        assert!(error.context().is_none());
156    }
157}