Skip to main content

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.
54#[cfg(not(target_arch = "wasm32"))]
55pub async fn check_rate_limit(client: &octocrab::Octocrab) -> Result<RateLimitStatus> {
56    debug!("Checking GitHub API rate limit");
57
58    let rate_limit = client.ratelimit().get().await?;
59
60    #[allow(clippy::cast_possible_truncation)]
61    let status = RateLimitStatus {
62        remaining: rate_limit.resources.core.remaining as u32,
63        limit: rate_limit.resources.core.limit as u32,
64        reset_at: rate_limit.resources.core.reset,
65    };
66
67    debug!(
68        remaining = status.remaining,
69        limit = status.limit,
70        "GitHub rate limit status"
71    );
72
73    Ok(status)
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn test_rate_limit_status_is_low_true() {
82        let status = RateLimitStatus {
83            remaining: 50,
84            limit: 5000,
85            reset_at: 1_234_567_890,
86        };
87        assert!(status.is_low());
88    }
89
90    #[test]
91    fn test_rate_limit_status_is_low_false() {
92        let status = RateLimitStatus {
93            remaining: 150,
94            limit: 5000,
95            reset_at: 1_234_567_890,
96        };
97        assert!(!status.is_low());
98    }
99
100    #[test]
101    fn test_rate_limit_status_is_low_boundary() {
102        let status = RateLimitStatus {
103            remaining: 100,
104            limit: 5000,
105            reset_at: 1_234_567_890,
106        };
107        assert!(!status.is_low());
108    }
109
110    #[test]
111    fn test_rate_limit_status_message() {
112        let status = RateLimitStatus {
113            remaining: 42,
114            limit: 5000,
115            reset_at: 1_234_567_890,
116        };
117        assert_eq!(status.message(), "GitHub API: 42/5000 calls remaining");
118    }
119}