use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum UpstreamFailureClass {
Connection,
Timeout,
RetryableStatus,
ContextWindow,
ModelUnavailable,
Authentication,
InvalidRequest,
Other,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct UpstreamFailure {
pub status: Option<u16>,
pub body: String,
pub headers: BTreeMap<String, String>,
pub class: UpstreamFailureClass,
}
impl UpstreamFailure {
pub fn is_retryable(&self) -> bool {
matches!(
self.class,
UpstreamFailureClass::Connection
| UpstreamFailureClass::Timeout
| UpstreamFailureClass::RetryableStatus
| UpstreamFailureClass::ContextWindow
| UpstreamFailureClass::ModelUnavailable
)
}
}
impl std::fmt::Display for UpstreamFailure {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.status {
Some(status) => write!(
formatter,
"upstream provider returned HTTP {status} ({:?}): {}",
self.class, self.body
),
None => write!(
formatter,
"upstream provider transport failure ({:?}): {}",
self.class, self.body
),
}
}
}
#[derive(Clone, Debug, Error)]
pub enum FlowError {
#[error("already exists: {0}")]
AlreadyExists(String),
#[error("not found: {0}")]
NotFound(String),
#[error("invalid argument: {0}")]
InvalidArgument(String),
#[error("scope stack empty")]
ScopeStackEmpty,
#[error("guardrail rejected: {0}")]
GuardrailRejected(String),
#[error("{0}")]
Upstream(UpstreamFailure),
#[error("internal error: {0}")]
Internal(String),
}
pub type Result<T> = std::result::Result<T, FlowError>;
#[cfg(test)]
#[path = "../tests/coverage/error_tests.rs"]
mod tests;