Skip to main content

steer_workspace/
error.rs

1#[cfg(feature = "schema")]
2use schemars::JsonSchema;
3use serde::{Deserialize, Serialize};
4use thiserror::Error;
5
6pub fn non_unique_match_preview_suffix(
7    match_previews: &[EditMatchPreview],
8    omitted_matches: usize,
9) -> String {
10    if match_previews.is_empty() {
11        return String::new();
12    }
13
14    let mut parts = match_previews
15        .iter()
16        .map(|preview| {
17            format!(
18                "line {}:{} `{}`",
19                preview.line_number, preview.column_number, preview.snippet
20            )
21        })
22        .collect::<Vec<_>>();
23
24    if omitted_matches > 0 {
25        parts.push(format!("and {omitted_matches} more"));
26    }
27
28    format!("; matches: {}", parts.join("; "))
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
32#[cfg_attr(feature = "schema", derive(JsonSchema))]
33pub struct EditMatchPreview {
34    pub line_number: usize,
35    pub column_number: usize,
36    pub snippet: String,
37}
38
39#[derive(Error, Debug, Clone, Serialize, Deserialize)]
40#[serde(tag = "code", content = "details", rename_all = "snake_case")]
41pub enum EditFailure {
42    #[error("file not found: {file_path}")]
43    FileNotFound { file_path: String },
44
45    #[error(
46        "edit #{edit_index} has an empty old_string; use write_file to create or overwrite files"
47    )]
48    EmptyOldString { edit_index: usize },
49
50    #[error("string not found for edit #{edit_index} in file {file_path}")]
51    StringNotFound {
52        file_path: String,
53        edit_index: usize,
54    },
55
56    #[error("invalid match selection for edit #{edit_index} in file {file_path}: {message}")]
57    InvalidMatchSelection {
58        file_path: String,
59        edit_index: usize,
60        message: String,
61    },
62
63    #[error(
64        "found {occurrences} matches for edit #{edit_index} in file {file_path}; old_string must match exactly once{preview_suffix}",
65        preview_suffix = non_unique_match_preview_suffix(match_previews, *omitted_matches)
66    )]
67    NonUniqueMatch {
68        file_path: String,
69        edit_index: usize,
70        occurrences: usize,
71        #[serde(default)]
72        match_previews: Vec<EditMatchPreview>,
73        #[serde(default)]
74        omitted_matches: usize,
75    },
76}
77
78#[derive(Error, Debug, Clone, Serialize, Deserialize)]
79pub enum WorkspaceError {
80    #[error("I/O error: {0}")]
81    Io(String),
82
83    #[error("Tool execution failed: {0}")]
84    ToolExecution(String),
85
86    #[error("Edit failed: {0}")]
87    Edit(EditFailure),
88
89    #[error("Transport error: {0}")]
90    Transport(String),
91
92    #[error("Status error: {0}")]
93    Status(String),
94
95    #[error("Not supported: {0}")]
96    NotSupported(String),
97
98    #[error("Invalid configuration: {0}")]
99    InvalidConfiguration(String),
100
101    #[error("Remote workspace error: {0}")]
102    Remote(String),
103}
104
105pub type Result<T> = std::result::Result<T, WorkspaceError>;
106
107#[derive(Error, Debug, Clone, Serialize, Deserialize)]
108pub enum EnvironmentManagerError {
109    #[error("Environment not found: {0}")]
110    NotFound(String),
111
112    #[error("Environment operation not supported: {0}")]
113    NotSupported(String),
114
115    #[error("Invalid environment request: {0}")]
116    InvalidRequest(String),
117
118    #[error("I/O error: {0}")]
119    Io(String),
120
121    #[error("Environment manager error: {0}")]
122    Other(String),
123}
124
125pub type EnvironmentManagerResult<T> = std::result::Result<T, EnvironmentManagerError>;
126
127impl From<std::io::Error> for EnvironmentManagerError {
128    fn from(err: std::io::Error) -> Self {
129        EnvironmentManagerError::Io(err.to_string())
130    }
131}
132
133impl From<WorkspaceError> for EnvironmentManagerError {
134    fn from(err: WorkspaceError) -> Self {
135        match err {
136            WorkspaceError::Io(message) => EnvironmentManagerError::Io(message),
137            other => EnvironmentManagerError::Other(other.to_string()),
138        }
139    }
140}
141
142#[derive(Error, Debug, Clone, Serialize, Deserialize)]
143pub enum WorkspaceManagerError {
144    #[error("Workspace not found: {0}")]
145    NotFound(String),
146
147    #[error("Workspace operation not supported: {0}")]
148    NotSupported(String),
149
150    #[error("Invalid workspace request: {0}")]
151    InvalidRequest(String),
152
153    #[error("I/O error: {0}")]
154    Io(String),
155
156    #[error("Workspace manager error: {0}")]
157    Other(String),
158}
159
160pub type WorkspaceManagerResult<T> = std::result::Result<T, WorkspaceManagerError>;
161
162impl From<std::io::Error> for WorkspaceManagerError {
163    fn from(err: std::io::Error) -> Self {
164        WorkspaceManagerError::Io(err.to_string())
165    }
166}
167
168impl From<WorkspaceError> for WorkspaceManagerError {
169    fn from(err: WorkspaceError) -> Self {
170        match err {
171            WorkspaceError::Io(message) => WorkspaceManagerError::Io(message),
172            other => WorkspaceManagerError::Other(other.to_string()),
173        }
174    }
175}
176
177impl From<tonic::transport::Error> for WorkspaceError {
178    fn from(err: tonic::transport::Error) -> Self {
179        WorkspaceError::Transport(err.to_string())
180    }
181}
182
183impl From<tonic::Status> for WorkspaceError {
184    fn from(err: tonic::Status) -> Self {
185        WorkspaceError::Status(err.to_string())
186    }
187}
188
189impl From<std::io::Error> for WorkspaceError {
190    fn from(err: std::io::Error) -> Self {
191        WorkspaceError::Io(err.to_string())
192    }
193}
194
195impl From<EditFailure> for WorkspaceError {
196    fn from(err: EditFailure) -> Self {
197        WorkspaceError::Edit(err)
198    }
199}