use thiserror::Error;
#[derive(Debug, Error)]
pub enum SwarmError {
#[error("LLM error (transient): {message}")]
LlmTransient { message: String },
#[error("Network error (transient): {message}")]
NetworkTransient { message: String },
#[error("Timeout: {message}")]
Timeout { message: String },
#[error("Resource busy: {message}")]
ResourceBusy { message: String },
#[error("LLM error: {message}")]
LlmPermanent { message: String },
#[error("Configuration error: {message}")]
Config { message: String },
#[error("Parse error: {message}")]
Parse { message: String },
#[error("Validation error: {message}")]
Validation { message: String },
#[error("Unknown action: {action}")]
UnknownAction { action: String },
#[error("Missing parameter: {param}")]
MissingParam { param: String },
#[error("Invalid parameter: {param}")]
InvalidParam { param: String },
#[error("Async task error: {message}")]
AsyncTask { message: String },
#[error("Internal error: {message}")]
Internal { message: String },
#[error(
"DependencyGraph is required but not available. \
Configure via: dependency_provider(), batch_invoker() with plan_dependencies support, \
or extension(DependencyGraph). \
Hint: {hint}"
)]
MissingDependencyGraph { hint: String },
#[error("No workers configured. Add workers via OrchestratorBuilder::add_worker()")]
NoWorkers,
#[error("Orchestrator configuration error: {message}")]
OrchestratorConfig { message: String },
#[error(transparent)]
ActionValidation(#[from] crate::actions::ActionValidationError),
#[error(transparent)]
DependencyGraph(#[from] crate::exploration::DependencyGraphError),
#[error(transparent)]
ConfigFile(#[from] crate::config::ConfigError),
}
impl SwarmError {
pub fn llm_transient(msg: impl Into<String>) -> Self {
Self::LlmTransient {
message: msg.into(),
}
}
pub fn llm_permanent(msg: impl Into<String>) -> Self {
Self::LlmPermanent {
message: msg.into(),
}
}
pub fn network_transient(msg: impl Into<String>) -> Self {
Self::NetworkTransient {
message: msg.into(),
}
}
pub fn timeout(msg: impl Into<String>) -> Self {
Self::Timeout {
message: msg.into(),
}
}
pub fn config(msg: impl Into<String>) -> Self {
Self::Config {
message: msg.into(),
}
}
pub fn parse(msg: impl Into<String>) -> Self {
Self::Parse {
message: msg.into(),
}
}
pub fn validation(msg: impl Into<String>) -> Self {
Self::Validation {
message: msg.into(),
}
}
pub fn async_task(msg: impl Into<String>) -> Self {
Self::AsyncTask {
message: msg.into(),
}
}
pub fn internal(msg: impl Into<String>) -> Self {
Self::Internal {
message: msg.into(),
}
}
pub fn is_transient(&self) -> bool {
matches!(
self,
Self::LlmTransient { .. }
| Self::NetworkTransient { .. }
| Self::Timeout { .. }
| Self::ResourceBusy { .. }
)
}
pub fn message(&self) -> String {
match self {
Self::LlmTransient { message } => message.clone(),
Self::NetworkTransient { message } => message.clone(),
Self::Timeout { message } => message.clone(),
Self::ResourceBusy { message } => message.clone(),
Self::LlmPermanent { message } => message.clone(),
Self::Config { message } => message.clone(),
Self::Parse { message } => message.clone(),
Self::Validation { message } => message.clone(),
Self::UnknownAction { action } => action.clone(),
Self::MissingParam { param } => param.clone(),
Self::InvalidParam { param } => param.clone(),
Self::AsyncTask { message } => message.clone(),
Self::Internal { message } => message.clone(),
Self::MissingDependencyGraph { hint } => hint.clone(),
Self::NoWorkers => "No workers configured".to_string(),
Self::OrchestratorConfig { message } => message.clone(),
Self::ActionValidation(e) => e.to_string(),
Self::DependencyGraph(e) => e.to_string(),
Self::ConfigFile(e) => e.to_string(),
}
}
pub fn kind(&self) -> &'static str {
match self {
Self::LlmTransient { .. } => "llm_transient",
Self::NetworkTransient { .. } => "network_transient",
Self::Timeout { .. } => "timeout",
Self::ResourceBusy { .. } => "resource_busy",
Self::LlmPermanent { .. } => "llm_permanent",
Self::Config { .. } => "config",
Self::Parse { .. } => "parse",
Self::Validation { .. } => "validation",
Self::UnknownAction { .. } => "unknown_action",
Self::MissingParam { .. } => "missing_param",
Self::InvalidParam { .. } => "invalid_param",
Self::AsyncTask { .. } => "async_task",
Self::Internal { .. } => "internal",
Self::MissingDependencyGraph { .. } => "missing_dependency_graph",
Self::NoWorkers => "no_workers",
Self::OrchestratorConfig { .. } => "orchestrator_config",
Self::ActionValidation(_) => "action_validation",
Self::DependencyGraph(_) => "dependency_graph",
Self::ConfigFile(_) => "config_file",
}
}
}
pub type SwarmResult<T> = Result<T, SwarmError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_transient() {
assert!(SwarmError::llm_transient("timeout").is_transient());
assert!(SwarmError::network_transient("connection refused").is_transient());
assert!(SwarmError::timeout("5s exceeded").is_transient());
assert!(!SwarmError::llm_permanent("invalid model").is_transient());
assert!(!SwarmError::config("missing field").is_transient());
assert!(!SwarmError::parse("invalid json").is_transient());
}
#[test]
fn test_message() {
let err = SwarmError::config("missing api_key");
assert_eq!(err.message(), "missing api_key");
}
#[test]
fn test_kind() {
assert_eq!(SwarmError::llm_transient("x").kind(), "llm_transient");
assert_eq!(SwarmError::config("x").kind(), "config");
}
#[test]
fn test_display() {
let err = SwarmError::llm_transient("connection timeout");
assert_eq!(
format!("{}", err),
"LLM error (transient): connection timeout"
);
}
}