prlens 0.1.1

One queue for all your PRs — aggregates GitHub and Bitbucket review requests into a single interactive view
Documentation
use async_trait::async_trait;
use crate::models::{PullRequest, PrIdentifier};

pub mod registry;
pub mod mock;
pub mod github;
pub mod bitbucket;

#[derive(Debug, Clone, PartialEq)]
pub enum AuthStatus {
    Available,
    Missing { reason: String },
}

#[derive(Debug, thiserror::Error)]
pub enum ProviderError {
    #[error("Auth not available for provider '{provider}': {reason}")]
    AuthMissing { provider: String, reason: String },

    #[error("API request failed for '{provider}': HTTP {status} — {message}")]
    ApiError { provider: String, status: u16, message: String },

    #[error("Rate limit exceeded for '{provider}', resets at {reset_at}")]
    RateLimited { provider: String, reset_at: chrono::DateTime<chrono::Utc> },

    #[error("Failed to parse response from '{provider}': {message}")]
    ParseError { provider: String, message: String },

    #[error("Operation not implemented for provider '{provider}'")]
    NotImplemented { provider: String },

    #[error("I/O error for provider '{provider}': {message}")]
    IoError { provider: String, message: String },
}

#[async_trait]
pub trait Provider: Send + Sync {
    /// Machine-readable provider identifier: "github", "bitbucket", "mock"
    fn name(&self) -> &'static str;

    /// Human-readable display name: "GitHub", "Bitbucket"
    fn display_name(&self) -> &'static str;

    /// Check auth availability WITHOUT making an API call.
    async fn check_auth(&self) -> AuthStatus;

    /// Fetch PRs awaiting review. Returns typed error for partial-result handling.
    async fn list_prs(&self) -> Result<Vec<PullRequest>, ProviderError>;

    /// Fetch single PR details (called on demand, not during list).
    async fn get_pr_details(&self, pr_id: &PrIdentifier) -> Result<PullRequest, ProviderError>;

    /// Fetch PR diff. Returns NotImplemented for providers that don't support it yet.
    async fn get_pr_diff(&self, pr_id: &PrIdentifier) -> Result<String, ProviderError>;
}

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

    #[test]
    fn provider_error_display() {
        let e = ProviderError::NotImplemented { provider: "mock".to_string() };
        assert!(e.to_string().to_lowercase().contains("not implemented"));
    }
}