cloudiful-rate-limiter 0.1.0

Reusable async resource throttling with local and Valkey-backed backends.
Documentation
use std::error::Error;
use std::fmt::{self, Display, Formatter};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InvalidPolicyKind {
    ZeroInterval,
    ZeroRatePerMinute,
    IntervalOutOfRange,
}

#[derive(Debug)]
pub struct InvalidPolicyError {
    kind: InvalidPolicyKind,
    message: &'static str,
}

impl InvalidPolicyError {
    pub fn kind(&self) -> InvalidPolicyKind {
        self.kind
    }

    pub fn message(&self) -> &'static str {
        self.message
    }

    pub(crate) fn zero_interval() -> Self {
        Self {
            kind: InvalidPolicyKind::ZeroInterval,
            message: "min interval must be greater than zero",
        }
    }

    pub(crate) fn zero_rpm() -> Self {
        Self {
            kind: InvalidPolicyKind::ZeroRatePerMinute,
            message: "per-minute rate must be greater than zero",
        }
    }

    pub(crate) fn interval_out_of_range() -> Self {
        Self {
            kind: InvalidPolicyKind::IntervalOutOfRange,
            message: "policy interval is too large to represent",
        }
    }
}

impl Display for InvalidPolicyError {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        f.write_str(self.message)
    }
}

impl Error for InvalidPolicyError {}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RateLimitErrorKind {
    InvalidPolicy,
    Connection,
    Backend,
    Time,
    Unknown,
}

#[derive(Debug)]
pub enum RateLimitError {
    InvalidPolicy(InvalidPolicyError),
    Backend {
        kind: RateLimitErrorKind,
        source: Box<dyn Error + Send + Sync>,
    },
}

impl RateLimitError {
    pub fn kind(&self) -> RateLimitErrorKind {
        match self {
            Self::InvalidPolicy(_) => RateLimitErrorKind::InvalidPolicy,
            Self::Backend { kind, .. } => *kind,
        }
    }

    #[cfg(feature = "valkey")]
    pub(crate) fn backend<E>(kind: RateLimitErrorKind, source: E) -> Self
    where
        E: Error + Send + Sync + 'static,
    {
        Self::Backend {
            kind,
            source: Box::new(source),
        }
    }
}

impl Display for RateLimitError {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            Self::InvalidPolicy(error) => write!(f, "invalid policy: {error}"),
            Self::Backend { source, .. } => write!(f, "{source}"),
        }
    }
}

impl Error for RateLimitError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            Self::InvalidPolicy(error) => Some(error),
            Self::Backend { source, .. } => Some(source.as_ref()),
        }
    }
}

impl From<InvalidPolicyError> for RateLimitError {
    fn from(value: InvalidPolicyError) -> Self {
        Self::InvalidPolicy(value)
    }
}

#[cfg(feature = "valkey")]
#[derive(Debug)]
pub enum ValkeyError {
    Redis(redis::RedisError),
    InvalidTime(std::time::SystemTimeError),
    IntervalOutOfRange(std::num::TryFromIntError),
}

#[cfg(feature = "valkey")]
impl Display for ValkeyError {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            Self::Redis(error) => write!(f, "{error}"),
            Self::InvalidTime(error) => write!(f, "{error}"),
            Self::IntervalOutOfRange(error) => write!(f, "{error}"),
        }
    }
}

#[cfg(feature = "valkey")]
impl Error for ValkeyError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            Self::Redis(error) => Some(error),
            Self::InvalidTime(error) => Some(error),
            Self::IntervalOutOfRange(error) => Some(error),
        }
    }
}