github-mcp 0.1.3

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::fmt;
use std::future::Future;
use std::sync::Mutex;
use std::time::{Duration, Instant};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum State {
    Closed,
    Open,
    HalfOpen,
}

struct Inner {
    state: State,
    failure_count: u32,
    opened_at: Option<Instant>,
}

/// `CircuitBreaker::execute`'s error type: either the breaker itself was
/// open (call never ran) or the wrapped call's own error `E` propagated
/// through. Generic over `E` rather than folding into `McpifyError` the way
/// `targets::typescript`'s `CircuitBreaker.execute` folds every rejection
/// into a re-thrown `unknown`, since Rust's `Result` makes losing the
/// original error type a real, avoidable cost here.
#[derive(Debug)]
pub enum CircuitBreakerError<E> {
    Open,
    Inner(E),
}

impl<E: fmt::Display> fmt::Display for CircuitBreakerError<E> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Open => write!(f, "circuit breaker is open"),
            Self::Inner(err) => write!(f, "{err}"),
        }
    }
}

impl<E: fmt::Debug + fmt::Display> std::error::Error for CircuitBreakerError<E> {}

/// CLOSED -> OPEN -> HALF_OPEN state machine around outbound calls to the
/// target API (REQ-2.3.3). Opens after `failure_threshold` consecutive
/// failures; after `reset_timeout`, lets one trial call through
/// (HALF_OPEN) to decide whether to close again or re-open.
pub struct CircuitBreaker {
    failure_threshold: u32,
    reset_timeout: Duration,
    inner: Mutex<Inner>,
}

impl CircuitBreaker {
    pub fn new(failure_threshold: u32, reset_timeout: Duration) -> Self {
        Self {
            failure_threshold,
            reset_timeout,
            inner: Mutex::new(Inner {
                state: State::Closed,
                failure_count: 0,
                opened_at: None,
            }),
        }
    }

    pub async fn execute<F, Fut, T, E>(&self, f: F) -> Result<T, CircuitBreakerError<E>>
    where
        F: FnOnce() -> Fut,
        Fut: Future<Output = Result<T, E>>,
    {
        {
            let mut inner = self.inner.lock().unwrap();
            if inner.state == State::Open {
                let elapsed = inner.opened_at.map(|at| at.elapsed()).unwrap_or_default();
                if elapsed < self.reset_timeout {
                    return Err(CircuitBreakerError::Open);
                }
                inner.state = State::HalfOpen;
            }
        }

        match f().await {
            Ok(value) => {
                self.on_success();
                Ok(value)
            }
            Err(err) => {
                self.on_failure();
                Err(CircuitBreakerError::Inner(err))
            }
        }
    }

    fn on_success(&self) {
        let mut inner = self.inner.lock().unwrap();
        inner.failure_count = 0;
        inner.state = State::Closed;
    }

    fn on_failure(&self) {
        let mut inner = self.inner.lock().unwrap();
        inner.failure_count += 1;
        if inner.state == State::HalfOpen || inner.failure_count >= self.failure_threshold {
            inner.state = State::Open;
            inner.opened_at = Some(Instant::now());
        }
    }
}

impl Default for CircuitBreaker {
    fn default() -> Self {
        Self::new(5, Duration::from_secs(30))
    }
}

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

    #[tokio::test]
    async fn opens_after_the_failure_threshold_is_reached() {
        let breaker = CircuitBreaker::new(2, Duration::from_secs(30));

        for _ in 0..2 {
            let _ = breaker
                .execute(|| async { Err::<(), _>(anyhow::anyhow!("boom")) })
                .await;
        }

        let result = breaker
            .execute(|| async { Ok::<_, anyhow::Error>(()) })
            .await;
        assert!(matches!(result, Err(CircuitBreakerError::Open)));
    }

    #[tokio::test]
    async fn a_success_resets_the_failure_count() {
        let breaker = CircuitBreaker::new(2, Duration::from_secs(30));

        let _ = breaker
            .execute(|| async { Err::<(), _>(anyhow::anyhow!("boom")) })
            .await;
        breaker
            .execute(|| async { Ok::<_, anyhow::Error>(()) })
            .await
            .unwrap();

        // A single further failure shouldn't open a breaker with
        // threshold 2, since the prior success reset the counter — the
        // error should be the wrapped call's own error, not `Open`.
        let result = breaker
            .execute(|| async { Err::<(), _>(anyhow::anyhow!("boom")) })
            .await;
        assert!(matches!(result, Err(CircuitBreakerError::Inner(_))));

        let ok = breaker
            .execute(|| async { Ok::<_, anyhow::Error>(()) })
            .await;
        assert!(ok.is_ok());
    }
}