tiny_counter/
error.rs

1// Stdlib imports
2use std::result;
3
4// Third-party imports
5use chrono::Duration;
6use thiserror::Error as ThisError;
7
8// Crate imports
9use crate::store::limiter::Constraint;
10
11/// Error returned when a rate limit is exceeded.
12#[derive(Debug)]
13pub struct LimitExceeded {
14    pub event_id: String,
15    pub constraint: Constraint,
16    pub retry_after: Option<Duration>,
17}
18
19/// Error types for the event-counter library.
20#[derive(Debug, ThisError)]
21pub enum Error {
22    #[error("future events cannot be recorded")]
23    FutureEvent,
24    #[error("event not found: {0}")]
25    EventNotFound(String),
26    #[error("storage error: {0}")]
27    Storage(String),
28    #[error("serialization error: {0}")]
29    Serialization(String),
30    #[error("config mismatch: {0}")]
31    ConfigMismatch(String),
32    #[error("invalid hour: {0} (must be 0-23)")]
33    InvalidHour(u8),
34    #[error("invalid hour range: start_hour {0} >= end_hour {1}")]
35    InvalidRange(u8, u8),
36    #[error("invalid bucket count: {0}")]
37    InvalidBucketCount(String),
38    #[error("TimeUnit::Ever cannot be used in IntervalConfig (it is a query-time sentinel value)")]
39    InvalidTimeUnitEver,
40    #[error("auto_persist interval must be positive (got {0})")]
41    InvalidAutoPersistInterval(Duration),
42    #[cfg(feature = "tokio")]
43    #[error("auto_persist requires storage to be configured - use with_storage() or remove auto_persist()")]
44    AutoPersistRequiresStorage,
45    #[error("storage requires a formatter - enable 'serde-bincode' or 'serde-json' feature, or use with_format() to provide a custom formatter")]
46    NoFormatterForStorage,
47    #[error("rate limit exceeded for event: {}", .0.event_id)]
48    LimitExceeded(LimitExceeded),
49}
50
51/// Convenience type for Results in this library.
52pub type Result<T> = result::Result<T, Error>;
53
54impl Error {
55    /// Returns a reference to the LimitExceeded details if this is a LimitExceeded error.
56    pub fn as_limit_exceeded(&self) -> Option<&LimitExceeded> {
57        match self {
58            Error::LimitExceeded(limit_exceeded) => Some(limit_exceeded),
59            _ => None,
60        }
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67
68    #[test]
69    fn test_future_event_error_display() {
70        let err = Error::FutureEvent;
71        assert_eq!(err.to_string(), "future events cannot be recorded");
72    }
73
74    #[test]
75    fn test_event_not_found_error_display() {
76        let err = Error::EventNotFound("test_event".to_string());
77        assert_eq!(err.to_string(), "event not found: test_event");
78    }
79
80    #[test]
81    fn test_storage_error_display() {
82        let err = Error::Storage("connection failed".to_string());
83        assert_eq!(err.to_string(), "storage error: connection failed");
84    }
85
86    #[test]
87    fn test_serialization_error_display() {
88        let err = Error::Serialization("invalid JSON".to_string());
89        assert_eq!(err.to_string(), "serialization error: invalid JSON");
90    }
91
92    #[test]
93    fn test_config_mismatch_error_display() {
94        let err = Error::ConfigMismatch("different time units".to_string());
95        assert_eq!(err.to_string(), "config mismatch: different time units");
96    }
97
98    #[test]
99    fn test_invalid_hour_error_display() {
100        let err = Error::InvalidHour(24);
101        assert_eq!(err.to_string(), "invalid hour: 24 (must be 0-23)");
102    }
103
104    #[test]
105    fn test_invalid_range_error_display() {
106        let err = Error::InvalidRange(17, 9);
107        assert_eq!(
108            err.to_string(),
109            "invalid hour range: start_hour 17 >= end_hour 9"
110        );
111    }
112
113    #[test]
114    fn test_invalid_bucket_count_error_display() {
115        let err = Error::InvalidBucketCount("bucket count must be positive".to_string());
116        assert_eq!(
117            err.to_string(),
118            "invalid bucket count: bucket count must be positive"
119        );
120    }
121
122    #[test]
123    fn test_invalid_time_unit_ever_error_display() {
124        let err = Error::InvalidTimeUnitEver;
125        assert_eq!(
126            err.to_string(),
127            "TimeUnit::Ever cannot be used in IntervalConfig (it is a query-time sentinel value)"
128        );
129    }
130
131    #[test]
132    fn test_error_is_send_sync() {
133        fn assert_send<T: Send>() {}
134        fn assert_sync<T: Sync>() {}
135        assert_send::<Error>();
136        assert_sync::<Error>();
137    }
138
139    #[test]
140    fn test_invalid_auto_persist_interval_error_display() {
141        let err = Error::InvalidAutoPersistInterval(Duration::seconds(-60));
142        assert_eq!(
143            err.to_string(),
144            "auto_persist interval must be positive (got -PT60S)"
145        );
146    }
147}