1use std::result;
3
4use chrono::Duration;
6use thiserror::Error as ThisError;
7
8use crate::store::limiter::Constraint;
10
11#[derive(Debug)]
13pub struct LimitExceeded {
14 pub event_id: String,
15 pub constraint: Constraint,
16 pub retry_after: Option<Duration>,
17}
18
19#[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
51pub type Result<T> = result::Result<T, Error>;
53
54impl Error {
55 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}