1use serde::Serialize;
2use thiserror::Error;
3
4#[derive(Error, Debug)]
5pub enum IntentError {
6 #[error("Database error: {0}")]
7 DatabaseError(#[from] sqlx::Error),
8
9 #[error("IO error: {0}")]
10 IoError(#[from] std::io::Error),
11
12 #[error("Task not found: {0}")]
13 TaskNotFound(i64),
14
15 #[error("Invalid input: {0}")]
16 InvalidInput(String),
17
18 #[error("Circular dependency detected")]
19 CircularDependency,
20
21 #[error("Action not allowed: {0}")]
22 ActionNotAllowed(String),
23
24 #[error("Uncompleted children exist")]
25 UncompletedChildren,
26
27 #[error("Current directory is not an Intent-Engine project")]
28 NotAProject,
29
30 #[error("JSON serialization error: {0}")]
31 JsonError(#[from] serde_json::Error),
32}
33
34#[derive(Serialize)]
35pub struct ErrorResponse {
36 pub error: String,
37 pub code: String,
38}
39
40impl IntentError {
41 pub fn to_error_code(&self) -> &'static str {
42 match self {
43 IntentError::TaskNotFound(_) => "TASK_NOT_FOUND",
44 IntentError::DatabaseError(_) => "DATABASE_ERROR",
45 IntentError::InvalidInput(_) => "INVALID_INPUT",
46 IntentError::CircularDependency => "CIRCULAR_DEPENDENCY",
47 IntentError::ActionNotAllowed(_) => "ACTION_NOT_ALLOWED",
48 IntentError::UncompletedChildren => "UNCOMPLETED_CHILDREN",
49 IntentError::NotAProject => "NOT_A_PROJECT",
50 _ => "INTERNAL_ERROR",
51 }
52 }
53
54 pub fn to_error_response(&self) -> ErrorResponse {
55 ErrorResponse {
56 error: self.to_string(),
57 code: self.to_error_code().to_string(),
58 }
59 }
60}
61
62pub type Result<T> = std::result::Result<T, IntentError>;
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 #[test]
69 fn test_task_not_found_error() {
70 let error = IntentError::TaskNotFound(123);
71 assert_eq!(error.to_string(), "Task not found: 123");
72 assert_eq!(error.to_error_code(), "TASK_NOT_FOUND");
73 }
74
75 #[test]
76 fn test_invalid_input_error() {
77 let error = IntentError::InvalidInput("Bad input".to_string());
78 assert_eq!(error.to_string(), "Invalid input: Bad input");
79 assert_eq!(error.to_error_code(), "INVALID_INPUT");
80 }
81
82 #[test]
83 fn test_circular_dependency_error() {
84 let error = IntentError::CircularDependency;
85 assert_eq!(error.to_string(), "Circular dependency detected");
86 assert_eq!(error.to_error_code(), "CIRCULAR_DEPENDENCY");
87 }
88
89 #[test]
90 fn test_action_not_allowed_error() {
91 let error = IntentError::ActionNotAllowed("Cannot do this".to_string());
92 assert_eq!(error.to_string(), "Action not allowed: Cannot do this");
93 assert_eq!(error.to_error_code(), "ACTION_NOT_ALLOWED");
94 }
95
96 #[test]
97 fn test_uncompleted_children_error() {
98 let error = IntentError::UncompletedChildren;
99 assert_eq!(error.to_string(), "Uncompleted children exist");
100 assert_eq!(error.to_error_code(), "UNCOMPLETED_CHILDREN");
101 }
102
103 #[test]
104 fn test_not_a_project_error() {
105 let error = IntentError::NotAProject;
106 assert_eq!(
107 error.to_string(),
108 "Current directory is not an Intent-Engine project"
109 );
110 assert_eq!(error.to_error_code(), "NOT_A_PROJECT");
111 }
112
113 #[test]
114 fn test_io_error_conversion() {
115 let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "File not found");
116 let error: IntentError = io_error.into();
117 assert!(matches!(error, IntentError::IoError(_)));
118 }
119
120 #[test]
121 fn test_json_error_conversion() {
122 let json_str = "{invalid json";
123 let json_error = serde_json::from_str::<serde_json::Value>(json_str).unwrap_err();
124 let error: IntentError = json_error.into();
125 assert!(matches!(error, IntentError::JsonError(_)));
126 }
127
128 #[test]
129 fn test_error_response_structure() {
130 let error = IntentError::TaskNotFound(456);
131 let response = error.to_error_response();
132
133 assert_eq!(response.code, "TASK_NOT_FOUND");
134 assert_eq!(response.error, "Task not found: 456");
135 }
136
137 #[test]
138 fn test_error_response_serialization() {
139 let error = IntentError::InvalidInput("Test".to_string());
140 let response = error.to_error_response();
141 let json = serde_json::to_string(&response).unwrap();
142
143 assert!(json.contains("\"code\":\"INVALID_INPUT\""));
144 assert!(json.contains("\"error\":\"Invalid input: Test\""));
145 }
146
147 #[test]
148 fn test_database_error_code() {
149 let error = IntentError::TaskNotFound(1);
151 if let IntentError::DatabaseError(_) = error {
152 unreachable!()
153 }
154 }
155
156 #[test]
157 fn test_internal_error_fallback() {
158 let io_error = std::io::Error::other("test");
160 let error: IntentError = io_error.into();
161 assert_eq!(error.to_error_code(), "INTERNAL_ERROR");
162 }
163}