1use serde::{Deserialize, Deserializer, Serialize, Serializer};
2use std::fmt;
3
4#[derive(Debug, Clone, PartialEq, Eq, Hash)]
6pub enum RateLimitStatus {
7 Allowed,
9 AllowedWarning,
11 Rejected,
13 Unknown(String),
15}
16
17impl RateLimitStatus {
18 pub fn as_str(&self) -> &str {
19 match self {
20 Self::Allowed => "allowed",
21 Self::AllowedWarning => "allowed_warning",
22 Self::Rejected => "rejected",
23 Self::Unknown(s) => s.as_str(),
24 }
25 }
26}
27
28impl fmt::Display for RateLimitStatus {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 f.write_str(self.as_str())
31 }
32}
33
34impl From<&str> for RateLimitStatus {
35 fn from(s: &str) -> Self {
36 match s {
37 "allowed" => Self::Allowed,
38 "allowed_warning" => Self::AllowedWarning,
39 "rejected" => Self::Rejected,
40 other => Self::Unknown(other.to_string()),
41 }
42 }
43}
44
45impl Serialize for RateLimitStatus {
46 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
47 serializer.serialize_str(self.as_str())
48 }
49}
50
51impl<'de> Deserialize<'de> for RateLimitStatus {
52 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
53 let s = String::deserialize(deserializer)?;
54 Ok(Self::from(s.as_str()))
55 }
56}
57
58#[derive(Debug, Clone, PartialEq, Eq, Hash)]
60pub enum RateLimitWindow {
61 FiveHour,
63 Hourly,
65 SevenDay,
67 Unknown(String),
69}
70
71impl RateLimitWindow {
72 pub fn as_str(&self) -> &str {
73 match self {
74 Self::FiveHour => "five_hour",
75 Self::Hourly => "hourly",
76 Self::SevenDay => "seven_day",
77 Self::Unknown(s) => s.as_str(),
78 }
79 }
80}
81
82impl fmt::Display for RateLimitWindow {
83 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84 f.write_str(self.as_str())
85 }
86}
87
88impl From<&str> for RateLimitWindow {
89 fn from(s: &str) -> Self {
90 match s {
91 "five_hour" => Self::FiveHour,
92 "hourly" => Self::Hourly,
93 "seven_day" => Self::SevenDay,
94 other => Self::Unknown(other.to_string()),
95 }
96 }
97}
98
99impl Serialize for RateLimitWindow {
100 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
101 serializer.serialize_str(self.as_str())
102 }
103}
104
105impl<'de> Deserialize<'de> for RateLimitWindow {
106 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
107 let s = String::deserialize(deserializer)?;
108 Ok(Self::from(s.as_str()))
109 }
110}
111
112#[derive(Debug, Clone, PartialEq, Eq, Hash)]
114pub enum OverageStatus {
115 Allowed,
117 Rejected,
119 Unknown(String),
121}
122
123impl OverageStatus {
124 pub fn as_str(&self) -> &str {
125 match self {
126 Self::Allowed => "allowed",
127 Self::Rejected => "rejected",
128 Self::Unknown(s) => s.as_str(),
129 }
130 }
131}
132
133impl fmt::Display for OverageStatus {
134 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135 f.write_str(self.as_str())
136 }
137}
138
139impl From<&str> for OverageStatus {
140 fn from(s: &str) -> Self {
141 match s {
142 "allowed" => Self::Allowed,
143 "rejected" => Self::Rejected,
144 other => Self::Unknown(other.to_string()),
145 }
146 }
147}
148
149impl Serialize for OverageStatus {
150 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
151 serializer.serialize_str(self.as_str())
152 }
153}
154
155impl<'de> Deserialize<'de> for OverageStatus {
156 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
157 let s = String::deserialize(deserializer)?;
158 Ok(Self::from(s.as_str()))
159 }
160}
161
162#[derive(Debug, Clone, PartialEq, Eq, Hash)]
164pub enum OverageDisabledReason {
165 OrgLevelDisabled,
167 OutOfCredits,
169 Unknown(String),
171}
172
173impl OverageDisabledReason {
174 pub fn as_str(&self) -> &str {
175 match self {
176 Self::OrgLevelDisabled => "org_level_disabled",
177 Self::OutOfCredits => "out_of_credits",
178 Self::Unknown(s) => s.as_str(),
179 }
180 }
181}
182
183impl fmt::Display for OverageDisabledReason {
184 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185 f.write_str(self.as_str())
186 }
187}
188
189impl From<&str> for OverageDisabledReason {
190 fn from(s: &str) -> Self {
191 match s {
192 "org_level_disabled" => Self::OrgLevelDisabled,
193 "out_of_credits" => Self::OutOfCredits,
194 other => Self::Unknown(other.to_string()),
195 }
196 }
197}
198
199impl Serialize for OverageDisabledReason {
200 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
201 serializer.serialize_str(self.as_str())
202 }
203}
204
205impl<'de> Deserialize<'de> for OverageDisabledReason {
206 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
207 let s = String::deserialize(deserializer)?;
208 Ok(Self::from(s.as_str()))
209 }
210}
211
212#[derive(Debug, Clone, Serialize, Deserialize)]
251pub struct RateLimitEvent {
252 pub rate_limit_info: RateLimitInfo,
254 pub session_id: String,
256 #[serde(skip_serializing_if = "Option::is_none")]
258 pub uuid: Option<String>,
259}
260
261#[derive(Debug, Clone, Serialize, Deserialize)]
263pub struct RateLimitInfo {
264 pub status: RateLimitStatus,
266 #[serde(rename = "resetsAt", skip_serializing_if = "Option::is_none")]
268 pub resets_at: Option<u64>,
269 #[serde(rename = "rateLimitType", skip_serializing_if = "Option::is_none")]
271 pub rate_limit_type: Option<RateLimitWindow>,
272 #[serde(skip_serializing_if = "Option::is_none")]
274 pub utilization: Option<f64>,
275 #[serde(skip_serializing_if = "Option::is_none", rename = "overageStatus")]
277 pub overage_status: Option<OverageStatus>,
278 #[serde(rename = "overageDisabledReason")]
280 pub overage_disabled_reason: Option<OverageDisabledReason>,
281 #[serde(rename = "isUsingOverage")]
283 pub is_using_overage: bool,
284}
285
286#[cfg(test)]
287mod tests {
288 use super::{OverageDisabledReason, OverageStatus, RateLimitStatus, RateLimitWindow};
289 use crate::io::ClaudeOutput;
290
291 #[test]
292 fn test_deserialize_rate_limit_event() {
293 let json = r#"{"type":"rate_limit_event","rate_limit_info":{"status":"allowed","resetsAt":1771390800,"rateLimitType":"five_hour","overageStatus":"rejected","overageDisabledReason":"org_level_disabled","isUsingOverage":false},"uuid":"76258cfb-0dc8-4d4b-8682-77082b59c03f","session_id":"1ae0af5b-89fa-4075-8156-d5d3702f6505"}"#;
294
295 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
296 assert!(output.is_rate_limit_event());
297 assert_eq!(output.message_type(), "rate_limit_event");
298 assert_eq!(
299 output.session_id(),
300 Some("1ae0af5b-89fa-4075-8156-d5d3702f6505")
301 );
302
303 let evt = output.as_rate_limit_event().unwrap();
304 assert_eq!(evt.rate_limit_info.status, RateLimitStatus::Allowed);
305 assert_eq!(evt.rate_limit_info.resets_at, Some(1771390800));
306 assert_eq!(
307 evt.rate_limit_info.rate_limit_type,
308 Some(RateLimitWindow::FiveHour)
309 );
310 assert_eq!(evt.rate_limit_info.utilization, None);
311 assert_eq!(
312 evt.rate_limit_info.overage_status,
313 Some(OverageStatus::Rejected)
314 );
315 assert_eq!(
316 evt.rate_limit_info.overage_disabled_reason,
317 Some(OverageDisabledReason::OrgLevelDisabled)
318 );
319 assert!(!evt.rate_limit_info.is_using_overage);
320 assert_eq!(
321 evt.uuid,
322 Some("76258cfb-0dc8-4d4b-8682-77082b59c03f".to_string())
323 );
324 }
325
326 #[test]
327 fn test_deserialize_rate_limit_event_minimal() {
328 let json = r#"{"type":"rate_limit_event","rate_limit_info":{"status":"allowed","resetsAt":0,"rateLimitType":"hourly","overageStatus":"allowed","isUsingOverage":true},"session_id":"abc"}"#;
329
330 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
331 let evt = output.as_rate_limit_event().unwrap();
332 assert_eq!(evt.rate_limit_info.overage_disabled_reason, None);
333 assert!(evt.rate_limit_info.is_using_overage);
334 assert!(evt.uuid.is_none());
335 }
336
337 #[test]
338 fn test_deserialize_rate_limit_event_allowed_warning() {
339 let json = r#"{"type":"rate_limit_event","rate_limit_info":{"status":"allowed_warning","resetsAt":1700000000,"rateLimitType":"five_hour","utilization":0.85,"isUsingOverage":false},"uuid":"550e8400-e29b-41d4-a716-446655440000","session_id":"test-session-id"}"#;
340
341 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
342 let evt = output.as_rate_limit_event().unwrap();
343 assert_eq!(evt.rate_limit_info.status, RateLimitStatus::AllowedWarning);
344 assert_eq!(evt.rate_limit_info.utilization, Some(0.85));
345 assert_eq!(evt.rate_limit_info.overage_status, None);
346 assert_eq!(evt.rate_limit_info.overage_disabled_reason, None);
347 assert!(!evt.rate_limit_info.is_using_overage);
348 }
349
350 #[test]
351 fn test_deserialize_rate_limit_event_no_resets_at() {
352 let json = r#"{"type":"rate_limit_event","rate_limit_info":{"status":"allowed","isUsingOverage":false},"uuid":"4269273d-3b5e-40ae-9765-cb3c12284c44","session_id":"f9626cf7-4d88-4844-9bb1-cab96909fc7b"}"#;
353
354 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
355 let evt = output.as_rate_limit_event().unwrap();
356 assert_eq!(evt.rate_limit_info.status, RateLimitStatus::Allowed);
357 assert_eq!(evt.rate_limit_info.resets_at, None);
358 assert_eq!(evt.rate_limit_info.rate_limit_type, None);
359 assert!(!evt.rate_limit_info.is_using_overage);
360 }
361
362 #[test]
363 fn test_deserialize_rate_limit_event_rejected() {
364 let json = r#"{"type":"rate_limit_event","rate_limit_info":{"status":"rejected","resetsAt":1700003600,"rateLimitType":"seven_day","isUsingOverage":false,"overageStatus":"rejected","overageDisabledReason":"out_of_credits"},"uuid":"660e8400-e29b-41d4-a716-446655440001","session_id":"test-session-id"}"#;
365
366 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
367 let evt = output.as_rate_limit_event().unwrap();
368 assert_eq!(evt.rate_limit_info.status, RateLimitStatus::Rejected);
369 assert_eq!(
370 evt.rate_limit_info.rate_limit_type,
371 Some(RateLimitWindow::SevenDay)
372 );
373 assert_eq!(
374 evt.rate_limit_info.overage_status,
375 Some(OverageStatus::Rejected)
376 );
377 assert_eq!(
378 evt.rate_limit_info.overage_disabled_reason,
379 Some(OverageDisabledReason::OutOfCredits)
380 );
381 }
382}