1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
pub use crate::ffi::{ErrorCode, ErrorContext, FFIBoundary, FFIError, FFIResult};
use thiserror::Error;
/// Error type for command execution failures.
#[derive(Debug, Error)]
pub enum CommandError {
/// The command was executed but returned a non-zero exit code.
#[error("Command '{command}' failed with exit code {exit_code:?}: {stderr}")]
ExecutionFailed {
command: String,
exit_code: Option<i32>,
stderr: String,
stdout: String, // Useful for some cases even on failure
},
/// The command failed to launch (e.g., binary not found).
#[error("Failed to execute '{command}': {message}")]
LaunchFailed { command: String, message: String },
}
/// Error type for operation timeouts.
#[derive(Debug, Error)]
#[error("{message}")]
pub struct TimeoutError {
/// Human-readable description of the timeout.
pub message: String,
}
/// Type alias for FFIResult used in the runtime host.
pub type HostResult<T> = FFIResult<T>;
/// Type alias for FFIError used in the runtime host.
pub type HostError = FFIError;
/// Trait to convert results (e.g. anyhow::Result) into FFIResult.
pub trait IntoFFIResult<T> {
/// Convert this value into an `FFIResult`, mapping any error into an `FFIError`
/// according to the implementing type (for example, turning `Ok` into `Success`
/// and `Err` into an error with an appropriate `ErrorCode` and `ErrorContext`).
fn into_ffi_result(self) -> FFIResult<T>;
}
impl<T> IntoFFIResult<T> for anyhow::Result<T> {
fn into_ffi_result(self) -> FFIResult<T> {
match self {
Ok(val) => FFIResult::Success(val),
Err(e) => {
// Try to downcast to CommandError
if let Some(cmd_error) = e.downcast_ref::<CommandError>() {
match cmd_error {
CommandError::ExecutionFailed {
command,
exit_code,
stderr,
stdout,
} => {
// Heuristically map command failures: git commands -> GitError, others -> IoError.
let inferred_code = {
let trimmed = command.trim_start();
if trimmed.starts_with("git ") {
ErrorCode::GitError
} else {
ErrorCode::IoError
}
};
return FFIResult::Error(FFIError {
message: format!("Command failed: {}", command),
code: inferred_code,
context: Some(ErrorContext {
command: Some(command.clone()),
exit_code: *exit_code,
stderr: Some(stderr.clone()),
stdout: Some(stdout.clone()),
..Default::default()
}),
suggestion: None, // Can be improved with heuristic analysis of stderr
});
}
CommandError::LaunchFailed { command, message } => {
return FFIResult::Error(FFIError {
message: format!(
"Failed to launch command '{}': {}",
command, message
),
code: ErrorCode::IoError,
context: Some(ErrorContext {
command: Some(command.clone()),
..Default::default()
}),
suggestion: None,
});
}
}
}
// Check for timeout (typed)
if let Some(timeout_err) = e.downcast_ref::<TimeoutError>() {
return FFIResult::Error(FFIError {
message: timeout_err.message.clone(),
code: ErrorCode::Timeout,
context: None,
suggestion: Some(
"Try increasing the timeout or checking network connection."
.to_string(),
),
});
}
// Check for timeout (legacy string matching for other sources)
if e.to_string().to_lowercase().contains("timed out") {
return FFIResult::Error(FFIError {
message: format!("Operation timed out: {}", e),
code: ErrorCode::Timeout,
context: None,
suggestion: Some(
"Try increasing the timeout or checking network connection."
.to_string(),
),
});
}
// Default to InternalError
FFIResult::Error(FFIError {
message: e.to_string(),
code: ErrorCode::InternalError,
context: None,
suggestion: None,
})
}
}
}
}