swarm_engine_core/
error.rs1use thiserror::Error;
33
34#[derive(Debug, Error)]
36pub enum SwarmError {
37 #[error("LLM error (transient): {message}")]
42 LlmTransient { message: String },
43
44 #[error("Network error (transient): {message}")]
46 NetworkTransient { message: String },
47
48 #[error("Timeout: {message}")]
50 Timeout { message: String },
51
52 #[error("Resource busy: {message}")]
54 ResourceBusy { message: String },
55
56 #[error("LLM error: {message}")]
61 LlmPermanent { message: String },
62
63 #[error("Configuration error: {message}")]
65 Config { message: String },
66
67 #[error("Parse error: {message}")]
69 Parse { message: String },
70
71 #[error("Validation error: {message}")]
73 Validation { message: String },
74
75 #[error("Unknown action: {action}")]
77 UnknownAction { action: String },
78
79 #[error("Missing parameter: {param}")]
81 MissingParam { param: String },
82
83 #[error("Invalid parameter: {param}")]
85 InvalidParam { param: String },
86
87 #[error("Async task error: {message}")]
89 AsyncTask { message: String },
90
91 #[error("Internal error: {message}")]
93 Internal { message: String },
94
95 #[error(
106 "DependencyGraph is required but not available. \
107 Configure via: dependency_provider(), batch_invoker() with plan_dependencies support, \
108 or extension(DependencyGraph). \
109 Hint: {hint}"
110 )]
111 MissingDependencyGraph { hint: String },
112
113 #[error("No workers configured. Add workers via OrchestratorBuilder::add_worker()")]
115 NoWorkers,
116
117 #[error("Orchestrator configuration error: {message}")]
119 OrchestratorConfig { message: String },
120
121 #[error(transparent)]
126 ActionValidation(#[from] crate::actions::ActionValidationError),
127
128 #[error(transparent)]
130 DependencyGraph(#[from] crate::exploration::DependencyGraphError),
131
132 #[error(transparent)]
134 ConfigFile(#[from] crate::config::ConfigError),
135}
136
137impl SwarmError {
138 pub fn llm_transient(msg: impl Into<String>) -> Self {
144 Self::LlmTransient {
145 message: msg.into(),
146 }
147 }
148
149 pub fn llm_permanent(msg: impl Into<String>) -> Self {
151 Self::LlmPermanent {
152 message: msg.into(),
153 }
154 }
155
156 pub fn network_transient(msg: impl Into<String>) -> Self {
158 Self::NetworkTransient {
159 message: msg.into(),
160 }
161 }
162
163 pub fn timeout(msg: impl Into<String>) -> Self {
165 Self::Timeout {
166 message: msg.into(),
167 }
168 }
169
170 pub fn config(msg: impl Into<String>) -> Self {
172 Self::Config {
173 message: msg.into(),
174 }
175 }
176
177 pub fn parse(msg: impl Into<String>) -> Self {
179 Self::Parse {
180 message: msg.into(),
181 }
182 }
183
184 pub fn validation(msg: impl Into<String>) -> Self {
186 Self::Validation {
187 message: msg.into(),
188 }
189 }
190
191 pub fn async_task(msg: impl Into<String>) -> Self {
193 Self::AsyncTask {
194 message: msg.into(),
195 }
196 }
197
198 pub fn internal(msg: impl Into<String>) -> Self {
200 Self::Internal {
201 message: msg.into(),
202 }
203 }
204
205 pub fn is_transient(&self) -> bool {
211 matches!(
212 self,
213 Self::LlmTransient { .. }
214 | Self::NetworkTransient { .. }
215 | Self::Timeout { .. }
216 | Self::ResourceBusy { .. }
217 )
218 }
219
220 pub fn message(&self) -> String {
222 match self {
223 Self::LlmTransient { message } => message.clone(),
224 Self::NetworkTransient { message } => message.clone(),
225 Self::Timeout { message } => message.clone(),
226 Self::ResourceBusy { message } => message.clone(),
227 Self::LlmPermanent { message } => message.clone(),
228 Self::Config { message } => message.clone(),
229 Self::Parse { message } => message.clone(),
230 Self::Validation { message } => message.clone(),
231 Self::UnknownAction { action } => action.clone(),
232 Self::MissingParam { param } => param.clone(),
233 Self::InvalidParam { param } => param.clone(),
234 Self::AsyncTask { message } => message.clone(),
235 Self::Internal { message } => message.clone(),
236 Self::MissingDependencyGraph { hint } => hint.clone(),
237 Self::NoWorkers => "No workers configured".to_string(),
238 Self::OrchestratorConfig { message } => message.clone(),
239 Self::ActionValidation(e) => e.to_string(),
240 Self::DependencyGraph(e) => e.to_string(),
241 Self::ConfigFile(e) => e.to_string(),
242 }
243 }
244
245 pub fn kind(&self) -> &'static str {
247 match self {
248 Self::LlmTransient { .. } => "llm_transient",
249 Self::NetworkTransient { .. } => "network_transient",
250 Self::Timeout { .. } => "timeout",
251 Self::ResourceBusy { .. } => "resource_busy",
252 Self::LlmPermanent { .. } => "llm_permanent",
253 Self::Config { .. } => "config",
254 Self::Parse { .. } => "parse",
255 Self::Validation { .. } => "validation",
256 Self::UnknownAction { .. } => "unknown_action",
257 Self::MissingParam { .. } => "missing_param",
258 Self::InvalidParam { .. } => "invalid_param",
259 Self::AsyncTask { .. } => "async_task",
260 Self::Internal { .. } => "internal",
261 Self::MissingDependencyGraph { .. } => "missing_dependency_graph",
262 Self::NoWorkers => "no_workers",
263 Self::OrchestratorConfig { .. } => "orchestrator_config",
264 Self::ActionValidation(_) => "action_validation",
265 Self::DependencyGraph(_) => "dependency_graph",
266 Self::ConfigFile(_) => "config_file",
267 }
268 }
269}
270
271pub type SwarmResult<T> = Result<T, SwarmError>;
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277
278 #[test]
279 fn test_is_transient() {
280 assert!(SwarmError::llm_transient("timeout").is_transient());
281 assert!(SwarmError::network_transient("connection refused").is_transient());
282 assert!(SwarmError::timeout("5s exceeded").is_transient());
283
284 assert!(!SwarmError::llm_permanent("invalid model").is_transient());
285 assert!(!SwarmError::config("missing field").is_transient());
286 assert!(!SwarmError::parse("invalid json").is_transient());
287 }
288
289 #[test]
290 fn test_message() {
291 let err = SwarmError::config("missing api_key");
292 assert_eq!(err.message(), "missing api_key");
293 }
294
295 #[test]
296 fn test_kind() {
297 assert_eq!(SwarmError::llm_transient("x").kind(), "llm_transient");
298 assert_eq!(SwarmError::config("x").kind(), "config");
299 }
300
301 #[test]
302 fn test_display() {
303 let err = SwarmError::llm_transient("connection timeout");
304 assert_eq!(
305 format!("{}", err),
306 "LLM error (transient): connection timeout"
307 );
308 }
309}