paper-gap 0.2.3

Local CLI for finding research papers with missing, weak, or stale public code
Documentation
use crate::Error;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ProviderOutcome {
    pub provider: String,
    pub status: ProviderStatus,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub message: Option<String>,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ProviderStatus {
    Success,
    Unavailable,
    RateLimited,
    Failed,
}

impl ProviderOutcome {
    pub fn success(provider: impl Into<String>) -> Self {
        Self {
            provider: provider.into(),
            status: ProviderStatus::Success,
            message: None,
        }
    }

    pub fn from_error(provider: impl Into<String>, error: &Error) -> Self {
        let status = match error {
            Error::Reqwest(error)
                if error.status() == Some(reqwest::StatusCode::TOO_MANY_REQUESTS) =>
            {
                ProviderStatus::RateLimited
            }
            Error::Reqwest(error) if error.is_timeout() || error.is_connect() => {
                ProviderStatus::Unavailable
            }
            _ => ProviderStatus::Failed,
        };
        Self {
            provider: provider.into(),
            status,
            message: Some(error.to_string()),
        }
    }

    pub fn succeeded(&self) -> bool {
        self.status == ProviderStatus::Success
    }

    pub fn aggregate(provider: impl Into<String>, outcomes: &[Self]) -> Self {
        let status = outcomes
            .iter()
            .map(|outcome| outcome.status)
            .max_by_key(|status| match status {
                ProviderStatus::Success => 0,
                ProviderStatus::Unavailable => 1,
                ProviderStatus::RateLimited => 2,
                ProviderStatus::Failed => 3,
            })
            .unwrap_or(ProviderStatus::Success);
        let messages: Vec<_> = outcomes
            .iter()
            .filter_map(|outcome| outcome.message.as_deref())
            .collect();
        Self {
            provider: provider.into(),
            status,
            message: (!messages.is_empty()).then(|| messages.join("; ")),
        }
    }
}

pub fn default_success() -> ProviderOutcome {
    ProviderOutcome::success("legacy")
}

impl std::fmt::Display for ProviderStatus {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(match self {
            Self::Success => "success",
            Self::Unavailable => "unavailable",
            Self::RateLimited => "rate_limited",
            Self::Failed => "failed",
        })
    }
}