aptu_core/github/
ratelimit.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! GitHub API rate limit checking.
4//!
5//! Provides utilities to check and report on GitHub API rate limit status.
6
7use anyhow::Result;
8use tracing::debug;
9
10/// GitHub API rate limit status.
11#[derive(Debug, Clone)]
12pub struct RateLimitStatus {
13    /// Number of API calls remaining in the current rate limit window.
14    pub remaining: u32,
15    /// Total number of API calls allowed in the rate limit window.
16    pub limit: u32,
17    /// Unix timestamp when the rate limit resets.
18    pub reset_at: u64,
19}
20
21impl RateLimitStatus {
22    /// Returns true if rate limit is low (remaining < 100).
23    #[must_use]
24    pub fn is_low(&self) -> bool {
25        self.remaining < 100
26    }
27
28    /// Returns a human-readable status message.
29    #[must_use]
30    pub fn message(&self) -> String {
31        format!(
32            "GitHub API: {}/{} calls remaining",
33            self.remaining, self.limit
34        )
35    }
36}
37
38/// Checks the GitHub API rate limit status.
39///
40/// Uses the authenticated Octocrab client to fetch the current rate limit
41/// information from the GitHub API.
42///
43/// # Arguments
44///
45/// * `client` - Authenticated Octocrab client
46///
47/// # Returns
48///
49/// `RateLimitStatus` with current rate limit information
50///
51/// # Errors
52///
53/// Returns an error if the API request fails.
54pub async fn check_rate_limit(client: &octocrab::Octocrab) -> Result<RateLimitStatus> {
55    debug!("Checking GitHub API rate limit");
56
57    let rate_limit = client.ratelimit().get().await?;
58
59    #[allow(clippy::cast_possible_truncation)]
60    let status = RateLimitStatus {
61        remaining: rate_limit.resources.core.remaining as u32,
62        limit: rate_limit.resources.core.limit as u32,
63        reset_at: rate_limit.resources.core.reset,
64    };
65
66    debug!(
67        remaining = status.remaining,
68        limit = status.limit,
69        "GitHub rate limit status"
70    );
71
72    Ok(status)
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    #[test]
80    fn test_rate_limit_status_is_low_true() {
81        let status = RateLimitStatus {
82            remaining: 50,
83            limit: 5000,
84            reset_at: 1_234_567_890,
85        };
86        assert!(status.is_low());
87    }
88
89    #[test]
90    fn test_rate_limit_status_is_low_false() {
91        let status = RateLimitStatus {
92            remaining: 150,
93            limit: 5000,
94            reset_at: 1_234_567_890,
95        };
96        assert!(!status.is_low());
97    }
98
99    #[test]
100    fn test_rate_limit_status_is_low_boundary() {
101        let status = RateLimitStatus {
102            remaining: 100,
103            limit: 5000,
104            reset_at: 1_234_567_890,
105        };
106        assert!(!status.is_low());
107    }
108
109    #[test]
110    fn test_rate_limit_status_message() {
111        let status = RateLimitStatus {
112            remaining: 42,
113            limit: 5000,
114            reset_at: 1_234_567_890,
115        };
116        assert_eq!(status.message(), "GitHub API: 42/5000 calls remaining");
117    }
118}