use std::result;
use chrono::Duration;
use thiserror::Error as ThisError;
use crate::store::limiter::Constraint;
#[derive(Debug)]
pub struct LimitExceeded {
pub event_id: String,
pub constraint: Constraint,
pub retry_after: Option<Duration>,
}
#[derive(Debug, ThisError)]
pub enum Error {
#[error("future events cannot be recorded")]
FutureEvent,
#[error("event not found: {0}")]
EventNotFound(String),
#[error("storage error: {0}")]
Storage(String),
#[error("serialization error: {0}")]
Serialization(String),
#[error("config mismatch: {0}")]
ConfigMismatch(String),
#[error("invalid hour: {0} (must be 0-23)")]
InvalidHour(u8),
#[error("invalid hour range: start_hour {0} >= end_hour {1}")]
InvalidRange(u8, u8),
#[error("invalid bucket count: {0}")]
InvalidBucketCount(String),
#[error("TimeUnit::Ever cannot be used in IntervalConfig (it is a query-time sentinel value)")]
InvalidTimeUnitEver,
#[error("auto_persist interval must be positive (got {0})")]
InvalidAutoPersistInterval(Duration),
#[cfg(feature = "tokio")]
#[error("auto_persist requires storage to be configured - use with_storage() or remove auto_persist()")]
AutoPersistRequiresStorage,
#[error("storage requires a formatter - enable 'serde-bincode' or 'serde-json' feature, or use with_format() to provide a custom formatter")]
NoFormatterForStorage,
#[error("rate limit exceeded for event: {}", .0.event_id)]
LimitExceeded(LimitExceeded),
}
pub type Result<T> = result::Result<T, Error>;
impl Error {
pub fn as_limit_exceeded(&self) -> Option<&LimitExceeded> {
match self {
Error::LimitExceeded(limit_exceeded) => Some(limit_exceeded),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_future_event_error_display() {
let err = Error::FutureEvent;
assert_eq!(err.to_string(), "future events cannot be recorded");
}
#[test]
fn test_event_not_found_error_display() {
let err = Error::EventNotFound("test_event".to_string());
assert_eq!(err.to_string(), "event not found: test_event");
}
#[test]
fn test_storage_error_display() {
let err = Error::Storage("connection failed".to_string());
assert_eq!(err.to_string(), "storage error: connection failed");
}
#[test]
fn test_serialization_error_display() {
let err = Error::Serialization("invalid JSON".to_string());
assert_eq!(err.to_string(), "serialization error: invalid JSON");
}
#[test]
fn test_config_mismatch_error_display() {
let err = Error::ConfigMismatch("different time units".to_string());
assert_eq!(err.to_string(), "config mismatch: different time units");
}
#[test]
fn test_invalid_hour_error_display() {
let err = Error::InvalidHour(24);
assert_eq!(err.to_string(), "invalid hour: 24 (must be 0-23)");
}
#[test]
fn test_invalid_range_error_display() {
let err = Error::InvalidRange(17, 9);
assert_eq!(
err.to_string(),
"invalid hour range: start_hour 17 >= end_hour 9"
);
}
#[test]
fn test_invalid_bucket_count_error_display() {
let err = Error::InvalidBucketCount("bucket count must be positive".to_string());
assert_eq!(
err.to_string(),
"invalid bucket count: bucket count must be positive"
);
}
#[test]
fn test_invalid_time_unit_ever_error_display() {
let err = Error::InvalidTimeUnitEver;
assert_eq!(
err.to_string(),
"TimeUnit::Ever cannot be used in IntervalConfig (it is a query-time sentinel value)"
);
}
#[test]
fn test_error_is_send_sync() {
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
assert_send::<Error>();
assert_sync::<Error>();
}
#[test]
fn test_invalid_auto_persist_interval_error_display() {
let err = Error::InvalidAutoPersistInterval(Duration::seconds(-60));
assert_eq!(
err.to_string(),
"auto_persist interval must be positive (got -PT60S)"
);
}
}