metis_core/error/
conversions.rs

1//! Error conversion traits and utilities for consistent error handling across crates
2
3use crate::error::MetisError;
4
5/// Trait for converting errors with additional context
6pub trait ErrorContext<T> {
7    /// Add context to an error result
8    fn with_context<F>(self, f: F) -> Result<T, MetisError>
9    where
10        F: FnOnce() -> String;
11
12    /// Add static context to an error result
13    fn with_static_context(self, context: &'static str) -> Result<T, MetisError>;
14}
15
16impl<T, E> ErrorContext<T> for Result<T, E>
17where
18    E: Into<MetisError>,
19{
20    fn with_context<F>(self, f: F) -> Result<T, MetisError>
21    where
22        F: FnOnce() -> String,
23    {
24        self.map_err(|e| {
25            let base_error = e.into();
26            MetisError::ValidationFailed {
27                message: format!("{}: {}", f(), base_error),
28            }
29        })
30    }
31
32    fn with_static_context(self, context: &'static str) -> Result<T, MetisError> {
33        self.map_err(|e| {
34            let base_error = e.into();
35            MetisError::ValidationFailed {
36                message: format!("{}: {}", context, base_error),
37            }
38        })
39    }
40}
41
42/// Trait for creating user-friendly error messages from MetisError
43pub trait UserFriendlyError {
44    /// Convert to a user-friendly error message
45    fn to_user_message(&self) -> String;
46
47    /// Get error category for UI display
48    fn error_category(&self) -> ErrorCategory;
49}
50
51#[derive(Debug, Clone, PartialEq)]
52pub enum ErrorCategory {
53    Workspace,
54    Document,
55    Database,
56    FileSystem,
57    Validation,
58    Network,
59    Configuration,
60}
61
62impl UserFriendlyError for MetisError {
63    fn to_user_message(&self) -> String {
64        match self {
65            MetisError::DocumentNotFound { id } => {
66                format!(
67                    "Document '{}' could not be found. It may have been moved or deleted.",
68                    id
69                )
70            }
71            MetisError::InvalidDocumentType { document_type } => {
72                format!("'{}' is not a valid document type. Valid types are: vision, strategy, initiative, task, adr.", document_type)
73            }
74            MetisError::InvalidPhaseTransition { from, to, doc_type } => {
75                format!("Cannot transition {} from '{}' to '{}'. Please check the valid phase transitions for this document type.", doc_type, from, to)
76            }
77            MetisError::MissingRequiredField { field } => {
78                format!(
79                    "Required field '{}' is missing. Please provide this information.",
80                    field
81                )
82            }
83            MetisError::TemplateNotFound { template } => {
84                format!(
85                    "Template '{}' could not be found. Please check your template configuration.",
86                    template
87                )
88            }
89            MetisError::ValidationFailed { message } => {
90                format!("Validation failed: {}", message)
91            }
92            MetisError::ExitCriteriaNotMet {
93                missing_count,
94                total_count,
95            } => {
96                format!("{} of {} exit criteria are incomplete. Please complete all criteria before proceeding.", missing_count, total_count)
97            }
98            MetisError::Database(e) => {
99                format!("Database error: {}. Please try again or contact support if the issue persists.", e)
100            }
101            MetisError::Connection(e) => {
102                format!(
103                    "Database connection error: {}. Please check your database configuration.",
104                    e
105                )
106            }
107            MetisError::Io(e) => {
108                format!(
109                    "File system error: {}. Please check file permissions and try again.",
110                    e
111                )
112            }
113            MetisError::Json(e) => {
114                format!(
115                    "JSON parsing error: {}. Please check the document format.",
116                    e
117                )
118            }
119            MetisError::Yaml(e) => {
120                format!(
121                    "YAML parsing error: {}. Please check the document format.",
122                    e
123                )
124            }
125            _ => {
126                format!("An error occurred: {}. Please try again.", self)
127            }
128        }
129    }
130
131    fn error_category(&self) -> ErrorCategory {
132        match self {
133            MetisError::DocumentNotFound { .. }
134            | MetisError::InvalidDocumentType { .. }
135            | MetisError::TemplateNotFound { .. } => ErrorCategory::Document,
136
137            MetisError::Database(_) | MetisError::Connection(_) => ErrorCategory::Database,
138
139            MetisError::Io(_) => ErrorCategory::FileSystem,
140
141            MetisError::InvalidPhaseTransition { .. }
142            | MetisError::MissingRequiredField { .. }
143            | MetisError::ValidationFailed { .. }
144            | MetisError::ExitCriteriaNotMet { .. } => ErrorCategory::Validation,
145
146            MetisError::Json(_) | MetisError::Yaml(_) => ErrorCategory::Document,
147
148            _ => ErrorCategory::Configuration,
149        }
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    #[test]
158    fn test_error_context() {
159        let result: Result<(), std::io::Error> = Err(std::io::Error::new(
160            std::io::ErrorKind::NotFound,
161            "file not found",
162        ));
163
164        let with_context = result.with_static_context("Reading configuration file");
165        assert!(with_context.is_err());
166        assert!(with_context
167            .unwrap_err()
168            .to_string()
169            .contains("Reading configuration file"));
170    }
171
172    #[test]
173    fn test_user_friendly_error_document_not_found() {
174        let error = MetisError::DocumentNotFound {
175            id: "test-doc".to_string(),
176        };
177        let message = error.to_user_message();
178        assert!(message.contains("Document 'test-doc' could not be found"));
179        assert_eq!(error.error_category(), ErrorCategory::Document);
180    }
181
182    #[test]
183    fn test_user_friendly_error_validation() {
184        let error = MetisError::ValidationFailed {
185            message: "test validation".to_string(),
186        };
187        let message = error.to_user_message();
188        assert!(message.contains("Validation failed: test validation"));
189        assert_eq!(error.error_category(), ErrorCategory::Validation);
190    }
191}