github-mcp 0.1.0

GitHub v3 REST API MCP server, generated by mcpify.
Documentation
// GitHub v3 REST API MCP server — generated by mcpify. Do not hand-edit.

use std::sync::Mutex;
use std::time::{Duration, Instant};

use super::errors::McpifyError;

/// Sliding-window rate limiter: at most `limit` calls per `window` —
/// REQ-2.3.3.
pub struct RateLimiter {
    limit: usize,
    window: Duration,
    timestamps: Mutex<Vec<Instant>>,
}

impl RateLimiter {
    pub fn new(limit: usize, window: Duration) -> Self {
        Self {
            limit,
            window,
            timestamps: Mutex::new(Vec::new()),
        }
    }

    pub fn acquire(&self) -> Result<(), McpifyError> {
        let now = Instant::now();
        let mut timestamps = self.timestamps.lock().unwrap();
        timestamps.retain(|&t| now.duration_since(t) < self.window);

        if timestamps.len() >= self.limit {
            return Err(McpifyError::RateLimitExceeded);
        }
        timestamps.push(now);
        Ok(())
    }
}

impl Default for RateLimiter {
    fn default() -> Self {
        Self::new(100, Duration::from_secs(1))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn allows_calls_up_to_the_limit() {
        let limiter = RateLimiter::new(3, Duration::from_secs(1));
        for _ in 0..3 {
            assert!(limiter.acquire().is_ok());
        }
    }

    #[test]
    fn rejects_calls_beyond_the_limit_within_the_window() {
        let limiter = RateLimiter::new(1, Duration::from_secs(60));
        limiter.acquire().unwrap();
        let err = limiter.acquire().unwrap_err();
        assert_eq!(err.code(), "RATE_LIMIT_EXCEEDED");
    }
}