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("{0}")]
29 NoActiveCommand(String),
30
31 #[error("{0}")]
33 BackgroundSessionNotFound(String),
34
35 #[error("Failure: {action} cannot be empty")]
37 EmptyInteractiveInput { action: String },
38
39 #[error("{0}")]
41 InteractiveTargetNotRunning(String),
42
43 #[error("Failed to parse arguments: {0}")]
45 ArgumentParseError(String),
46
47 #[error("File access error for {path}: {message}")]
49 FileAccessError { path: PathBuf, message: String },
50
51 #[error("Security violation: {message}")]
53 PathSecurityError { path: PathBuf, message: String },
54
55 #[error("Command not allowed: {0}")]
57 CommandNotAllowed(String),
58
59 #[error("Thread ID mismatch: {0}")]
61 ThreadIdMismatch(String),
62
63 #[error("Deserialization error: {0}")]
65 DeserializationError(String),
66
67 #[error("Serialization error: {0}")]
69 SerializationError(String),
70
71 #[error("Search/replace syntax error: {0}")]
73 SearchReplaceSyntaxError(String),
74
75 #[error("Search block not found in content: {0}")]
77 SearchBlockNotFound(String),
78
79 #[error("Search block matched multiple times")]
81 SearchBlockAmbiguous { block_content: String, match_count: usize, suggestions: Vec<String> },
82
83 #[error(
87 "MultiFileEdit aborted before writing anything - file {index} ({path}) failed validation: {source}"
88 )]
89 MultiFilePlanError { index: usize, path: PathBuf, source: Box<WinxError> },
90
91 #[error("Search/replace syntax error: {message}")]
93 SearchReplaceSyntaxErrorDetailed {
94 message: String,
95 line_number: Option<usize>,
96 block_type: Option<String>,
97 suggestions: Vec<String>,
98 },
99
100 #[error("Invalid JSON: {0}")]
102 JsonParseError(String),
103
104 #[error("File {path} is too large: {size} bytes (max {max_size})")]
106 FileTooLarge { path: PathBuf, size: u64, max_size: u64 },
107
108 #[error("Failed to write file {path}: {message}")]
110 FileWriteError { path: PathBuf, message: String },
111
112 #[error("Failed to load data: {0}")]
114 DataLoadingError(String),
115
116 #[error("Invalid parameter: {field} - {message}")]
118 ParameterValidationError { field: String, message: String },
119
120 #[error("Required parameter missing: {field} - {message}")]
122 MissingParameterError { field: String, message: String },
123
124 #[error("Null or undefined value where object expected: {field}")]
126 NullValueError { field: String },
127
128 #[error("{message} - {suggestion}")]
130 RecoverableSuggestionError { message: String, suggestion: String },
131
132 #[error("Context save error: {0}")]
134 ContextSaveError(String),
135
136 #[error("Command timed out after {timeout_seconds}s: {command}")]
138 CommandTimeout { command: String, timeout_seconds: u64 },
139
140 #[error(
142 "Interactive command detected: {command}. Use appropriate flags or consider alternatives."
143 )]
144 InteractiveCommandDetected { command: String },
145
146 #[error("A command is already running: '{current_command}' (for {duration_seconds:.1}s). Use status_check, send_text, or interrupt.")]
148 CommandAlreadyRunning { current_command: String, duration_seconds: f64 },
149
150 #[error("Failed to cleanup process: {message}")]
152 ProcessCleanupError { message: String },
153
154 #[error("Command output exceeded maximum size: {size} bytes (max {max_size})")]
156 BufferOverflow { size: usize, max_size: usize },
157
158 #[error("Failed to recover bash session: {message}")]
160 SessionRecoveryError { message: String },
161
162 #[error("Resource allocation failed: {message}")]
164 ResourceAllocationError { message: String },
165
166 #[error("IO error: {0}")]
168 IoError(#[from] std::io::Error),
169
170 #[error("Configuration error: {0}")]
172 ConfigurationError(String),
173
174 #[error("Parse error: {0}")]
176 ParseError(String),
177
178 #[error("Invalid input: {0}")]
180 InvalidInput(String),
181
182 #[error("File error: {0}")]
184 FileError(String),
185}
186
187pub type Result<T> = std::result::Result<T, WinxError>;
189
190impl From<anyhow::Error> for WinxError {
192 fn from(error: anyhow::Error) -> Self {
193 WinxError::CommandExecutionError(format!("{error:#}"))
197 }
198}
199
200pub struct ErrorRecovery;
202
203impl ErrorRecovery {
204 pub fn suggest(error: WinxError, _suggestion: &str) -> WinxError {
205 error
206 }
207
208 pub fn param_error(field: &str, message: &str) -> WinxError {
209 WinxError::ParameterValidationError {
210 field: field.to_string(),
211 message: message.to_string(),
212 }
213 }
214
215 pub fn missing_param(field: &str, message: &str) -> WinxError {
216 WinxError::MissingParameterError { field: field.to_string(), message: message.to_string() }
217 }
218
219 pub fn null_value(field: &str) -> WinxError {
220 WinxError::NullValueError { field: field.to_string() }
221 }
222}
223
224impl Clone for WinxError {
226 #[allow(clippy::too_many_lines)] fn clone(&self) -> Self {
228 match self {
229 Self::ShellInitializationError(msg) => Self::ShellInitializationError(msg.clone()),
230 Self::WorkspacePathError(msg) => Self::WorkspacePathError(msg.clone()),
231 Self::BashStateLockError(msg) => Self::BashStateLockError(msg.clone()),
232 Self::BashStateNotInitialized => Self::BashStateNotInitialized,
233 Self::CommandExecutionError(msg) => Self::CommandExecutionError(msg.clone()),
234 Self::NoActiveCommand(msg) => Self::NoActiveCommand(msg.clone()),
235 Self::BackgroundSessionNotFound(msg) => Self::BackgroundSessionNotFound(msg.clone()),
236 Self::EmptyInteractiveInput { action } => {
237 Self::EmptyInteractiveInput { action: action.clone() }
238 }
239 Self::InteractiveTargetNotRunning(msg) => {
240 Self::InteractiveTargetNotRunning(msg.clone())
241 }
242 Self::CommandNotAllowed(msg) => Self::CommandNotAllowed(msg.clone()),
243 Self::ThreadIdMismatch(msg) => Self::ThreadIdMismatch(msg.clone()),
244 Self::ArgumentParseError(msg) => Self::ArgumentParseError(msg.clone()),
245 Self::FileAccessError { path, message } => {
246 Self::FileAccessError { path: path.clone(), message: message.clone() }
247 }
248 Self::DeserializationError(msg) => Self::DeserializationError(msg.clone()),
249 Self::SerializationError(msg) => Self::SerializationError(msg.clone()),
250 Self::SearchReplaceSyntaxError(msg) => Self::SearchReplaceSyntaxError(msg.clone()),
251 Self::SearchBlockNotFound(msg) => Self::SearchBlockNotFound(msg.clone()),
252 Self::SearchBlockAmbiguous { block_content, match_count, suggestions } => {
253 Self::SearchBlockAmbiguous {
254 block_content: block_content.clone(),
255 match_count: *match_count,
256 suggestions: suggestions.clone(),
257 }
258 }
259 Self::MultiFilePlanError { index, path, source } => Self::MultiFilePlanError {
260 index: *index,
261 path: path.clone(),
262 source: Box::new((**source).clone()),
263 },
264 Self::SearchReplaceSyntaxErrorDetailed {
265 message,
266 line_number,
267 block_type,
268 suggestions,
269 } => Self::SearchReplaceSyntaxErrorDetailed {
270 message: message.clone(),
271 line_number: *line_number,
272 block_type: block_type.clone(),
273 suggestions: suggestions.clone(),
274 },
275 Self::JsonParseError(msg) => Self::JsonParseError(msg.clone()),
276 Self::FileTooLarge { path, size, max_size } => {
277 Self::FileTooLarge { path: path.clone(), size: *size, max_size: *max_size }
278 }
279 Self::FileWriteError { path, message } => {
280 Self::FileWriteError { path: path.clone(), message: message.clone() }
281 }
282 Self::DataLoadingError(msg) => Self::DataLoadingError(msg.clone()),
283 Self::ParameterValidationError { field, message } => {
284 Self::ParameterValidationError { field: field.clone(), message: message.clone() }
285 }
286 Self::MissingParameterError { field, message } => {
287 Self::MissingParameterError { field: field.clone(), message: message.clone() }
288 }
289 Self::NullValueError { field } => Self::NullValueError { field: field.clone() },
290 Self::RecoverableSuggestionError { message, suggestion } => {
291 Self::RecoverableSuggestionError {
292 message: message.clone(),
293 suggestion: suggestion.clone(),
294 }
295 }
296 Self::ContextSaveError(msg) => Self::ContextSaveError(msg.clone()),
297 Self::CommandTimeout { command, timeout_seconds } => {
298 Self::CommandTimeout { command: command.clone(), timeout_seconds: *timeout_seconds }
299 }
300 Self::InteractiveCommandDetected { command } => {
301 Self::InteractiveCommandDetected { command: command.clone() }
302 }
303 Self::CommandAlreadyRunning { current_command, duration_seconds } => {
304 Self::CommandAlreadyRunning {
305 current_command: current_command.clone(),
306 duration_seconds: *duration_seconds,
307 }
308 }
309 Self::ProcessCleanupError { message } => {
310 Self::ProcessCleanupError { message: message.clone() }
311 }
312 Self::BufferOverflow { size, max_size } => {
313 Self::BufferOverflow { size: *size, max_size: *max_size }
314 }
315 Self::SessionRecoveryError { message } => {
316 Self::SessionRecoveryError { message: message.clone() }
317 }
318 Self::ResourceAllocationError { message } => {
319 Self::ResourceAllocationError { message: message.clone() }
320 }
321 Self::IoError(err) => Self::IoError(std::io::Error::new(err.kind(), err.to_string())),
322 Self::ConfigurationError(msg) => Self::ConfigurationError(msg.clone()),
323 Self::ParseError(msg) => Self::ParseError(msg.clone()),
324 Self::InvalidInput(msg) => Self::InvalidInput(msg.clone()),
325 Self::FileError(msg) => Self::FileError(msg.clone()),
326 Self::PathSecurityError { path, message } => {
327 Self::PathSecurityError { path: path.clone(), message: message.clone() }
328 }
329 }
330 }
331}