use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use thiserror::Error;
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[non_exhaustive]
pub enum RetrySafety {
Safe,
RequiresIdempotency,
UnsafeAfterVisibleOutput,
UnsafeAfterSideEffect,
Unknown,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[non_exhaustive]
pub enum RunErrorKind {
InvalidInput,
CapabilityDenied,
BudgetExceeded,
DeadlineExceeded,
Cancelled,
Transport,
Protocol,
Invocation,
Extension(String),
}
impl RunErrorKind {
pub fn code(&self) -> &str {
match self {
Self::InvalidInput => "runifold.invalid_input",
Self::CapabilityDenied => "runifold.capability_denied",
Self::BudgetExceeded => "runifold.budget_exceeded",
Self::DeadlineExceeded => "runifold.deadline_exceeded",
Self::Cancelled => "runifold.cancelled",
Self::Transport => "runifold.transport",
Self::Protocol => "runifold.protocol",
Self::Invocation => "runifold.invocation",
Self::Extension(namespace) => namespace,
}
}
pub fn recommendation(&self) -> &'static str {
match self {
Self::InvalidInput => "Validate configuration and request data before retrying.",
Self::CapabilityDenied => {
"Grant only the required capability or remove the unauthorized operation."
}
Self::BudgetExceeded => "Increase the explicit budget or reduce bounded work.",
Self::DeadlineExceeded => {
"Review the deadline and upstream latency before deciding whether to retry."
}
Self::Cancelled => "Do not retry unless the caller starts a new operation.",
Self::Transport => "Inspect retry safety, endpoint health, and network diagnostics.",
Self::Protocol => {
"Inspect the Provider response and adapter compatibility before retrying."
}
Self::Invocation => "Inspect the invoked component's typed cause and metadata.",
Self::Extension(_) => "Inspect the namespaced extension metadata and documentation.",
}
}
}
#[derive(Clone, Debug, Deserialize, Error, PartialEq, Serialize)]
#[error("{kind:?}: {message}")]
pub struct RunError {
pub kind: RunErrorKind,
pub message: String,
pub retry_safety: RetrySafety,
pub metadata: BTreeMap<String, Value>,
}
impl RunError {
pub fn code(&self) -> &str {
self.kind.code()
}
pub fn recommendation(&self) -> &'static str {
self.kind.recommendation()
}
}
#[cfg(test)]
mod tests {
use super::RunErrorKind;
#[test]
fn diagnostic_codes_are_stable_and_extension_aware() {
assert_eq!(RunErrorKind::Protocol.code(), "runifold.protocol");
assert_eq!(
RunErrorKind::Extension("acme.custom".into()).code(),
"acme.custom"
);
assert!(!RunErrorKind::Transport.recommendation().is_empty());
}
}