metis_core/error/
conversions.rs1use crate::error::MetisError;
4
5pub trait ErrorContext<T> {
7 fn with_context<F>(self, f: F) -> Result<T, MetisError>
9 where
10 F: FnOnce() -> String;
11
12 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
42pub trait UserFriendlyError {
44 fn to_user_message(&self) -> String;
46
47 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}