use core::fmt;
use error_forge::ForgeError;
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ThrottleError {
CostExceedsCapacity {
cost: u32,
capacity: u32,
},
CircuitOpen {
retry_after: core::time::Duration,
},
QueueFull,
DeadlineExceeded,
}
impl fmt::Display for ThrottleError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::CostExceedsCapacity { cost, capacity } => write!(
f,
"requested cost {cost} exceeds limiter capacity {capacity}; it can never be granted"
),
Self::CircuitOpen { retry_after } => write!(
f,
"circuit breaker is open; request shed, retry in {retry_after:?}"
),
Self::QueueFull => {
f.write_str("queue is full; request rejected by the overflow policy")
}
Self::DeadlineExceeded => {
f.write_str("request deadline passed before it could be served")
}
}
}
}
impl std::error::Error for ThrottleError {}
impl ForgeError for ThrottleError {
fn kind(&self) -> &'static str {
match self {
Self::CostExceedsCapacity { .. } => "CostExceedsCapacity",
Self::CircuitOpen { .. } => "CircuitOpen",
Self::QueueFull => "QueueFull",
Self::DeadlineExceeded => "DeadlineExceeded",
}
}
fn caption(&self) -> &'static str {
"Throttle acquisition error"
}
fn is_retryable(&self) -> bool {
match self {
Self::CostExceedsCapacity { .. } => false,
Self::CircuitOpen { .. } => true,
Self::QueueFull => true,
Self::DeadlineExceeded => false,
}
}
}
#[cfg(test)]
mod tests {
use super::ThrottleError;
use error_forge::ForgeError;
#[test]
fn test_display_names_both_values() {
let msg = ThrottleError::CostExceedsCapacity {
cost: 9,
capacity: 5,
}
.to_string();
assert!(msg.contains('9'));
assert!(msg.contains('5'));
}
#[test]
fn test_forge_kind_matches_variant() {
let err = ThrottleError::CostExceedsCapacity {
cost: 1,
capacity: 0,
};
assert_eq!(err.kind(), "CostExceedsCapacity");
}
#[test]
fn test_capacity_mismatch_is_not_retryable() {
let err = ThrottleError::CostExceedsCapacity {
cost: 9,
capacity: 5,
};
assert!(!err.is_retryable());
}
}