use std::fmt;
use std::time::Duration;
#[derive(Debug, Clone)]
pub enum FailureKind {
Transient {
backoff_base: Duration,
},
Logic {
context: String,
},
Critical {
alert: bool,
},
}
impl FailureKind {
pub fn transient() -> Self {
Self::Transient {
backoff_base: Duration::from_millis(500),
}
}
pub fn transient_with_backoff(base_ms: u64) -> Self {
Self::Transient {
backoff_base: Duration::from_millis(base_ms),
}
}
pub fn logic(context: impl Into<String>) -> Self {
Self::Logic {
context: context.into(),
}
}
pub fn critical() -> Self {
Self::Critical { alert: true }
}
pub fn critical_silent() -> Self {
Self::Critical { alert: false }
}
pub fn is_retryable(&self) -> bool {
matches!(self, Self::Transient { .. } | Self::Logic { .. })
}
}
impl fmt::Display for FailureKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Transient { backoff_base } => {
write!(f, "Transient(backoff={}ms)", backoff_base.as_millis())
}
Self::Logic { context } => write!(f, "Logic({})", context),
Self::Critical { alert } => write!(f, "Critical(alert={})", alert),
}
}
}
#[derive(Debug)]
pub struct AgentError {
pub kind: FailureKind,
pub message: String,
pub source: Option<Box<dyn std::error::Error + Send + Sync>>,
}
impl AgentError {
pub fn new(kind: FailureKind, message: impl Into<String>) -> Self {
Self {
kind,
message: message.into(),
source: None,
}
}
pub fn with_source(
kind: FailureKind,
message: impl Into<String>,
source: impl std::error::Error + Send + Sync + 'static,
) -> Self {
Self {
kind,
message: message.into(),
source: Some(Box::new(source)),
}
}
pub fn extract(err: &anyhow::Error) -> Option<&AgentError> {
err.downcast_ref::<AgentError>()
}
}
impl fmt::Display for AgentError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{}] {}", self.kind, self.message)
}
}
impl std::error::Error for AgentError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source
.as_ref()
.map(|s| s.as_ref() as &(dyn std::error::Error + 'static))
}
}