1use std::path::PathBuf;
2use thiserror::Error;
3
4#[derive(Error, Debug)]
6pub enum WinxError {
7 #[error("Failed to initialize shell: {0}")]
9 ShellInitializationError(String),
10
11 #[error("Workspace path error: {0}")]
13 WorkspacePathError(String),
14
15 #[error("Failed to lock the bash state: {0}")]
17 BashStateLockError(String),
18
19 #[error("Bash state not initialized. Please call Initialize first with type=\"first_call\" and a valid workspace path.")]
21 BashStateNotInitialized,
22
23 #[error("Command execution failed: {0}")]
25 CommandExecutionError(String),
26
27 #[error("Failed to parse arguments: {0}")]
29 ArgumentParseError(String),
30
31 #[error("File access error for {path}: {message}")]
33 FileAccessError { path: PathBuf, message: String },
34
35 #[error("Security violation: {message}")]
37 PathSecurityError { path: PathBuf, message: String },
38
39 #[error("Command not allowed: {0}")]
41 CommandNotAllowed(String),
42
43 #[error("Thread ID mismatch: {0}")]
45 ThreadIdMismatch(String),
46
47 #[error("Deserialization error: {0}")]
49 DeserializationError(String),
50
51 #[error("Serialization error: {0}")]
53 SerializationError(String),
54
55 #[error("Search/replace syntax error: {0}")]
57 SearchReplaceSyntaxError(String),
58
59 #[error("Search block not found in content: {0}")]
61 SearchBlockNotFound(String),
62
63 #[error("Search block matched multiple times")]
65 SearchBlockAmbiguous { block_content: String, match_count: usize, suggestions: Vec<String> },
66
67 #[error("Multiple search blocks have conflicting matches")]
69 SearchBlockConflict { conflicting_blocks: Vec<String>, first_differing_block: Option<String> },
70
71 #[error("Search/replace syntax error: {message}")]
73 SearchReplaceSyntaxErrorDetailed {
74 message: String,
75 line_number: Option<usize>,
76 block_type: Option<String>,
77 suggestions: Vec<String>,
78 },
79
80 #[error("Invalid JSON: {0}")]
82 JsonParseError(String),
83
84 #[error("File {path} is too large: {size} bytes (max {max_size})")]
86 FileTooLarge { path: PathBuf, size: u64, max_size: u64 },
87
88 #[error("Failed to write file {path}: {message}")]
90 FileWriteError { path: PathBuf, message: String },
91
92 #[error("Failed to load data: {0}")]
94 DataLoadingError(String),
95
96 #[error("Invalid parameter: {field} - {message}")]
98 ParameterValidationError { field: String, message: String },
99
100 #[error("Required parameter missing: {field} - {message}")]
102 MissingParameterError { field: String, message: String },
103
104 #[error("Null or undefined value where object expected: {field}")]
106 NullValueError { field: String },
107
108 #[error("{message} - {suggestion}")]
110 RecoverableSuggestionError { message: String, suggestion: String },
111
112 #[error("Context save error: {0}")]
114 ContextSaveError(String),
115
116 #[error("Command timed out after {timeout_seconds}s: {command}")]
118 CommandTimeout { command: String, timeout_seconds: u64 },
119
120 #[error(
122 "Interactive command detected: {command}. Use appropriate flags or consider alternatives."
123 )]
124 InteractiveCommandDetected { command: String },
125
126 #[error("A command is already running: '{current_command}' (for {duration_seconds:.1}s). Use status_check, send_text, or interrupt.")]
128 CommandAlreadyRunning { current_command: String, duration_seconds: f64 },
129
130 #[error("Failed to cleanup process: {message}")]
132 ProcessCleanupError { message: String },
133
134 #[error("Command output exceeded maximum size: {size} bytes (max {max_size})")]
136 BufferOverflow { size: usize, max_size: usize },
137
138 #[error("Failed to recover bash session: {message}")]
140 SessionRecoveryError { message: String },
141
142 #[error("Resource allocation failed: {message}")]
144 ResourceAllocationError { message: String },
145
146 #[error("IO error: {0}")]
148 IoError(#[from] std::io::Error),
149
150 #[error("Configuration error: {0}")]
152 ConfigurationError(String),
153
154 #[error("Parse error: {0}")]
156 ParseError(String),
157
158 #[error("Invalid input: {0}")]
160 InvalidInput(String),
161
162 #[error("File error: {0}")]
164 FileError(String),
165}
166
167pub type Result<T> = std::result::Result<T, WinxError>;
169
170impl From<anyhow::Error> for WinxError {
172 fn from(error: anyhow::Error) -> Self {
173 WinxError::CommandExecutionError(format!("{error:#}"))
177 }
178}
179
180pub struct ErrorRecovery;
182
183impl ErrorRecovery {
184 pub fn suggest(error: WinxError, _suggestion: &str) -> WinxError {
185 error
186 }
187
188 pub fn param_error(field: &str, message: &str) -> WinxError {
189 WinxError::ParameterValidationError {
190 field: field.to_string(),
191 message: message.to_string(),
192 }
193 }
194
195 pub fn missing_param(field: &str, message: &str) -> WinxError {
196 WinxError::MissingParameterError { field: field.to_string(), message: message.to_string() }
197 }
198
199 pub fn null_value(field: &str) -> WinxError {
200 WinxError::NullValueError { field: field.to_string() }
201 }
202}
203
204impl Clone for WinxError {
206 fn clone(&self) -> Self {
207 match self {
208 Self::ShellInitializationError(msg) => Self::ShellInitializationError(msg.clone()),
209 Self::WorkspacePathError(msg) => Self::WorkspacePathError(msg.clone()),
210 Self::BashStateLockError(msg) => Self::BashStateLockError(msg.clone()),
211 Self::BashStateNotInitialized => Self::BashStateNotInitialized,
212 Self::CommandExecutionError(msg) => Self::CommandExecutionError(msg.clone()),
213 Self::CommandNotAllowed(msg) => Self::CommandNotAllowed(msg.clone()),
214 Self::ThreadIdMismatch(msg) => Self::ThreadIdMismatch(msg.clone()),
215 Self::ArgumentParseError(msg) => Self::ArgumentParseError(msg.clone()),
216 Self::FileAccessError { path, message } => {
217 Self::FileAccessError { path: path.clone(), message: message.clone() }
218 }
219 Self::DeserializationError(msg) => Self::DeserializationError(msg.clone()),
220 Self::SerializationError(msg) => Self::SerializationError(msg.clone()),
221 Self::SearchReplaceSyntaxError(msg) => Self::SearchReplaceSyntaxError(msg.clone()),
222 Self::SearchBlockNotFound(msg) => Self::SearchBlockNotFound(msg.clone()),
223 Self::SearchBlockAmbiguous { block_content, match_count, suggestions } => {
224 Self::SearchBlockAmbiguous {
225 block_content: block_content.clone(),
226 match_count: *match_count,
227 suggestions: suggestions.clone(),
228 }
229 }
230 Self::SearchBlockConflict { conflicting_blocks, first_differing_block } => {
231 Self::SearchBlockConflict {
232 conflicting_blocks: conflicting_blocks.clone(),
233 first_differing_block: first_differing_block.clone(),
234 }
235 }
236 Self::SearchReplaceSyntaxErrorDetailed {
237 message,
238 line_number,
239 block_type,
240 suggestions,
241 } => Self::SearchReplaceSyntaxErrorDetailed {
242 message: message.clone(),
243 line_number: *line_number,
244 block_type: block_type.clone(),
245 suggestions: suggestions.clone(),
246 },
247 Self::JsonParseError(msg) => Self::JsonParseError(msg.clone()),
248 Self::FileTooLarge { path, size, max_size } => {
249 Self::FileTooLarge { path: path.clone(), size: *size, max_size: *max_size }
250 }
251 Self::FileWriteError { path, message } => {
252 Self::FileWriteError { path: path.clone(), message: message.clone() }
253 }
254 Self::DataLoadingError(msg) => Self::DataLoadingError(msg.clone()),
255 Self::ParameterValidationError { field, message } => {
256 Self::ParameterValidationError { field: field.clone(), message: message.clone() }
257 }
258 Self::MissingParameterError { field, message } => {
259 Self::MissingParameterError { field: field.clone(), message: message.clone() }
260 }
261 Self::NullValueError { field } => Self::NullValueError { field: field.clone() },
262 Self::RecoverableSuggestionError { message, suggestion } => {
263 Self::RecoverableSuggestionError {
264 message: message.clone(),
265 suggestion: suggestion.clone(),
266 }
267 }
268 Self::ContextSaveError(msg) => Self::ContextSaveError(msg.clone()),
269 Self::CommandTimeout { command, timeout_seconds } => {
270 Self::CommandTimeout { command: command.clone(), timeout_seconds: *timeout_seconds }
271 }
272 Self::InteractiveCommandDetected { command } => {
273 Self::InteractiveCommandDetected { command: command.clone() }
274 }
275 Self::CommandAlreadyRunning { current_command, duration_seconds } => {
276 Self::CommandAlreadyRunning {
277 current_command: current_command.clone(),
278 duration_seconds: *duration_seconds,
279 }
280 }
281 Self::ProcessCleanupError { message } => {
282 Self::ProcessCleanupError { message: message.clone() }
283 }
284 Self::BufferOverflow { size, max_size } => {
285 Self::BufferOverflow { size: *size, max_size: *max_size }
286 }
287 Self::SessionRecoveryError { message } => {
288 Self::SessionRecoveryError { message: message.clone() }
289 }
290 Self::ResourceAllocationError { message } => {
291 Self::ResourceAllocationError { message: message.clone() }
292 }
293 Self::IoError(err) => Self::IoError(std::io::Error::new(err.kind(), err.to_string())),
294 Self::ConfigurationError(msg) => Self::ConfigurationError(msg.clone()),
295 Self::ParseError(msg) => Self::ParseError(msg.clone()),
296 Self::InvalidInput(msg) => Self::InvalidInput(msg.clone()),
297 Self::FileError(msg) => Self::FileError(msg.clone()),
298 Self::PathSecurityError { path, message } => {
299 Self::PathSecurityError { path: path.clone(), message: message.clone() }
300 }
301 }
302 }
303}