workflow-utils 0.18.0

Miscellaneous utilities for the workflow-rs ecosystem.
Documentation
use crate::imports::*;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Version {
    pub major: u64,
    pub minor: u64,
    pub patch: u64,
}

impl AsRef<Version> for Version {
    fn as_ref(&self) -> &Version {
        self
    }
}

impl FromStr for Version {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self> {
        let mut parts = s.split('.');
        let major = parts
            .next()
            .ok_or_else(|| Error::custom("Invalid version"))?
            .chars()
            .filter(|c| c.is_ascii_digit())
            .collect::<String>()
            .parse()?;
        let minor = parts
            .next()
            .ok_or_else(|| Error::custom("Invalid version"))?
            .chars()
            .filter(|c| c.is_ascii_digit())
            .collect::<String>()
            .parse()?;
        let patch = parts
            .next()
            .ok_or_else(|| Error::custom("Invalid version"))?
            .chars()
            .filter(|c| c.is_ascii_digit())
            .collect::<String>()
            .parse()?;
        Ok(Version {
            major,
            minor,
            patch,
        })
    }
}

impl Display for Version {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
    }
}

impl Version {
    pub fn is_greater_than<V>(&self, other: V) -> bool
    where
        V: AsRef<Version>,
    {
        use std::cmp::Ordering;

        let other = other.as_ref();

        matches!(
            (
                self.major.cmp(&other.major),
                self.minor.cmp(&other.minor),
                self.patch.cmp(&other.patch),
            ),
            (Ordering::Greater, _, _)
                | (Ordering::Equal, Ordering::Greater, _)
                | (Ordering::Equal, Ordering::Equal, Ordering::Greater)
        )
    }
}

#[derive(Debug, Deserialize)]
struct CrateResponse {
    #[serde(rename = "crate")]
    crate_: Crate,
}

#[derive(Debug, Deserialize)]
struct Crate {
    max_version: String,
}

pub async fn latest_crate_version<S: Display, U: Display>(
    crate_name: S,
    user_agent: U,
) -> Result<Version> {
    let url = format!("https://crates.io/api/v1/crates/{crate_name}");
    let response = http::Request::new(url)
        .with_user_agent(user_agent.to_string())
        .get_json::<CrateResponse>()
        .await?;
    response.crate_.max_version.parse()
}

#[cfg(not(target_arch = "wasm32"))]
pub mod blocking {
    use super::*;
    use reqwest::blocking::Client;
    use reqwest::header::*;

    pub fn latest_crate_version<S: Display, U: Display>(
        crate_name: S,
        user_agent: U,
    ) -> Result<Version> {
        let url = format!("https://crates.io/api/v1/crates/{crate_name}");
        let mut headers = HeaderMap::new();
        headers.insert(
            USER_AGENT,
            HeaderValue::from_str(user_agent.to_string().as_str())?,
        );
        let response = Client::builder()
            .default_headers(headers)
            .build()?
            .get(url)
            .send()?
            .json::<CrateResponse>()?;
        response.crate_.max_version.parse()
    }
}