Skip to main content

claude_codes/io/
rate_limit.rs

1use serde::{Deserialize, Serialize};
2
3/// Rate limit event from Claude CLI.
4///
5/// Sent periodically to inform consumers about current rate limit status,
6/// including overage eligibility and reset timing.
7///
8/// # Example JSON
9///
10/// ```json
11/// {
12///   "type": "rate_limit_event",
13///   "rate_limit_info": {
14///     "status": "allowed",
15///     "resetsAt": 1771390800,
16///     "rateLimitType": "five_hour",
17///     "overageStatus": "rejected",
18///     "overageDisabledReason": "org_level_disabled",
19///     "isUsingOverage": false
20///   },
21///   "uuid": "76258cfb-0dc8-4d4b-8682-77082b59c03f",
22///   "session_id": "1ae0af5b-89fa-4075-8156-d5d3702f6505"
23/// }
24/// ```
25///
26/// # Example
27///
28/// ```
29/// use claude_codes::ClaudeOutput;
30///
31/// 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":"abc","session_id":"def"}"#;
32/// let output: ClaudeOutput = serde_json::from_str(json).unwrap();
33///
34/// if let Some(evt) = output.as_rate_limit_event() {
35///     println!("Rate limit status: {}", evt.rate_limit_info.status);
36///     println!("Resets at: {}", evt.rate_limit_info.resets_at);
37/// }
38/// ```
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct RateLimitEvent {
41    /// Rate limit status details
42    pub rate_limit_info: RateLimitInfo,
43    /// Session identifier
44    pub session_id: String,
45    /// Unique identifier for this message
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub uuid: Option<String>,
48}
49
50/// Rate limit status information.
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct RateLimitInfo {
53    /// Current rate limit status (e.g., "allowed")
54    pub status: String,
55    /// Unix timestamp when the rate limit resets
56    #[serde(rename = "resetsAt")]
57    pub resets_at: u64,
58    /// Type of rate limit (e.g., "five_hour")
59    #[serde(rename = "rateLimitType")]
60    pub rate_limit_type: String,
61    /// Utilization of the rate limit (0.0 to 1.0)
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub utilization: Option<f64>,
64    /// Overage status (e.g., "rejected", "allowed")
65    #[serde(skip_serializing_if = "Option::is_none", rename = "overageStatus")]
66    pub overage_status: Option<String>,
67    /// Reason overage is disabled, if applicable
68    #[serde(rename = "overageDisabledReason")]
69    pub overage_disabled_reason: Option<String>,
70    /// Whether overage billing is active
71    #[serde(rename = "isUsingOverage")]
72    pub is_using_overage: bool,
73}
74
75#[cfg(test)]
76mod tests {
77    use crate::io::ClaudeOutput;
78
79    #[test]
80    fn test_deserialize_rate_limit_event() {
81        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"}"#;
82
83        let output: ClaudeOutput = serde_json::from_str(json).unwrap();
84        assert!(output.is_rate_limit_event());
85        assert_eq!(output.message_type(), "rate_limit_event");
86        assert_eq!(
87            output.session_id(),
88            Some("1ae0af5b-89fa-4075-8156-d5d3702f6505")
89        );
90
91        let evt = output.as_rate_limit_event().unwrap();
92        assert_eq!(evt.rate_limit_info.status, "allowed");
93        assert_eq!(evt.rate_limit_info.resets_at, 1771390800);
94        assert_eq!(evt.rate_limit_info.rate_limit_type, "five_hour");
95        assert_eq!(evt.rate_limit_info.utilization, None);
96        assert_eq!(
97            evt.rate_limit_info.overage_status,
98            Some("rejected".to_string())
99        );
100        assert_eq!(
101            evt.rate_limit_info.overage_disabled_reason,
102            Some("org_level_disabled".to_string())
103        );
104        assert!(!evt.rate_limit_info.is_using_overage);
105        assert_eq!(
106            evt.uuid,
107            Some("76258cfb-0dc8-4d4b-8682-77082b59c03f".to_string())
108        );
109    }
110
111    #[test]
112    fn test_deserialize_rate_limit_event_minimal() {
113        let json = r#"{"type":"rate_limit_event","rate_limit_info":{"status":"allowed","resetsAt":0,"rateLimitType":"hourly","overageStatus":"allowed","isUsingOverage":true},"session_id":"abc"}"#;
114
115        let output: ClaudeOutput = serde_json::from_str(json).unwrap();
116        let evt = output.as_rate_limit_event().unwrap();
117        assert_eq!(evt.rate_limit_info.overage_disabled_reason, None);
118        assert!(evt.rate_limit_info.is_using_overage);
119        assert!(evt.uuid.is_none());
120    }
121
122    #[test]
123    fn test_deserialize_rate_limit_event_allowed_warning() {
124        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"}"#;
125
126        let output: ClaudeOutput = serde_json::from_str(json).unwrap();
127        let evt = output.as_rate_limit_event().unwrap();
128        assert_eq!(evt.rate_limit_info.status, "allowed_warning");
129        assert_eq!(evt.rate_limit_info.utilization, Some(0.85));
130        assert_eq!(evt.rate_limit_info.overage_status, None);
131        assert_eq!(evt.rate_limit_info.overage_disabled_reason, None);
132        assert!(!evt.rate_limit_info.is_using_overage);
133    }
134
135    #[test]
136    fn test_deserialize_rate_limit_event_rejected() {
137        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"}"#;
138
139        let output: ClaudeOutput = serde_json::from_str(json).unwrap();
140        let evt = output.as_rate_limit_event().unwrap();
141        assert_eq!(evt.rate_limit_info.status, "rejected");
142        assert_eq!(evt.rate_limit_info.rate_limit_type, "seven_day");
143        assert_eq!(
144            evt.rate_limit_info.overage_status,
145            Some("rejected".to_string())
146        );
147        assert_eq!(
148            evt.rate_limit_info.overage_disabled_reason,
149            Some("out_of_credits".to_string())
150        );
151    }
152}