use std::time::Duration;
use crate::RuntimeContractViolation;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FullPolicy {
Reject,
WaitUntilDeadline,
CoalesceLatest,
DropStale,
CloseSlowConsumer,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BudgetClass {
Data,
Control,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BudgetDimension {
Count,
Bytes,
Rate,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RateLimit {
pub permits_per_second: u64,
pub burst: u64,
}
impl RateLimit {
#[must_use]
pub const fn new(permits_per_second: u64, burst: u64) -> Self {
Self {
permits_per_second,
burst,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BudgetCapacity {
pub count: usize,
pub bytes: usize,
pub rate: Option<RateLimit>,
}
impl BudgetCapacity {
#[must_use]
pub const fn new(count: usize, bytes: usize) -> Self {
Self {
count,
bytes,
rate: None,
}
}
#[must_use]
pub const fn with_rate(mut self, rate: RateLimit) -> Self {
self.rate = Some(rate);
self
}
}
impl Default for BudgetCapacity {
fn default() -> Self {
Self::new(0, 0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BudgetLimit {
pub capacity: BudgetCapacity,
pub control_reserve: BudgetCapacity,
pub max_age: Option<Duration>,
pub full_policy: FullPolicy,
}
impl BudgetLimit {
#[must_use]
pub const fn new(count: usize, bytes: usize, full_policy: FullPolicy) -> Self {
Self {
capacity: BudgetCapacity::new(count, bytes),
control_reserve: BudgetCapacity::new(0, 0),
max_age: None,
full_policy,
}
}
#[must_use]
pub const fn with_rate(mut self, rate: RateLimit) -> Self {
self.capacity.rate = Some(rate);
self
}
#[must_use]
pub const fn with_control_reserve(mut self, reserve: BudgetCapacity) -> Self {
self.control_reserve = reserve;
self
}
#[must_use]
pub const fn with_max_age(mut self, max_age: Duration) -> Self {
self.max_age = Some(max_age);
self
}
pub(crate) fn validate(self, path: &str) -> Result<(), RuntimeContractViolation> {
if self.capacity.count == 0 {
return Err(RuntimeContractViolation::ZeroBudgetCapacity {
dimension: BudgetDimension::Count,
});
}
if self.capacity.bytes == 0 {
return Err(RuntimeContractViolation::ZeroBudgetCapacity {
dimension: BudgetDimension::Bytes,
});
}
validate_rate(path, self.capacity.rate)?;
validate_rate(path, self.control_reserve.rate)?;
if self.control_reserve.count > self.capacity.count {
return Err(RuntimeContractViolation::ReserveExceedsBudgetCapacity {
dimension: BudgetDimension::Count,
});
}
if self.control_reserve.bytes > self.capacity.bytes {
return Err(RuntimeContractViolation::ReserveExceedsBudgetCapacity {
dimension: BudgetDimension::Bytes,
});
}
match (self.capacity.rate, self.control_reserve.rate) {
(Some(capacity), Some(reserve))
if reserve.permits_per_second > capacity.permits_per_second || reserve.burst > capacity.burst =>
{
Err(RuntimeContractViolation::ReserveExceedsBudgetCapacity {
dimension: BudgetDimension::Rate,
})
}
(None, Some(_)) => Err(RuntimeContractViolation::ReserveWithoutBudgetCapacity {
dimension: BudgetDimension::Rate,
}),
_ if self.max_age == Some(Duration::ZERO) => Err(RuntimeContractViolation::ZeroBudgetMaxAge),
_ => Ok(()),
}
}
pub(crate) fn validate_child(self, parent: Self, path: &str) -> Result<(), RuntimeContractViolation> {
self.validate(path)?;
if self.capacity.count > parent.capacity.count {
return Err(RuntimeContractViolation::ChildBudgetExceedsParent {
dimension: BudgetDimension::Count,
});
}
if self.capacity.bytes > parent.capacity.bytes {
return Err(RuntimeContractViolation::ChildBudgetExceedsParent {
dimension: BudgetDimension::Bytes,
});
}
match (self.capacity.rate, parent.capacity.rate) {
(Some(child), Some(parent))
if child.permits_per_second > parent.permits_per_second || child.burst > parent.burst =>
{
return Err(RuntimeContractViolation::ChildBudgetExceedsParent {
dimension: BudgetDimension::Rate,
});
}
(None, Some(_)) => {
return Err(RuntimeContractViolation::ChildBudgetExceedsParent {
dimension: BudgetDimension::Rate,
});
}
_ => {}
}
match (self.max_age, parent.max_age) {
(Some(child), Some(parent)) if child > parent => {
Err(RuntimeContractViolation::ChildBudgetMaxAgeExceedsParent)
}
(None, Some(_)) => Err(RuntimeContractViolation::ChildBudgetMaxAgeExceedsParent),
_ => Ok(()),
}
}
}
fn validate_rate(_path: &str, rate: Option<RateLimit>) -> Result<(), RuntimeContractViolation> {
if let Some(rate) = rate {
if rate.permits_per_second == 0 || rate.burst == 0 {
return Err(RuntimeContractViolation::ZeroBudgetRate);
}
}
Ok(())
}