workflow_utils/
version.rs

1use crate::imports::*;
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4pub struct Version {
5    pub major: u64,
6    pub minor: u64,
7    pub patch: u64,
8}
9
10impl AsRef<Version> for Version {
11    fn as_ref(&self) -> &Version {
12        self
13    }
14}
15
16impl FromStr for Version {
17    type Err = Error;
18
19    fn from_str(s: &str) -> Result<Self> {
20        let mut parts = s.split('.');
21        let major = parts
22            .next()
23            .ok_or_else(|| Error::custom("Invalid version"))?
24            .chars()
25            .filter(|c| c.is_ascii_digit())
26            .collect::<String>()
27            .parse()?;
28        let minor = parts
29            .next()
30            .ok_or_else(|| Error::custom("Invalid version"))?
31            .chars()
32            .filter(|c| c.is_ascii_digit())
33            .collect::<String>()
34            .parse()?;
35        let patch = parts
36            .next()
37            .ok_or_else(|| Error::custom("Invalid version"))?
38            .chars()
39            .filter(|c| c.is_ascii_digit())
40            .collect::<String>()
41            .parse()?;
42        Ok(Version {
43            major,
44            minor,
45            patch,
46        })
47    }
48}
49
50impl Display for Version {
51    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
52        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
53    }
54}
55
56impl Version {
57    pub fn is_greater_than<V>(&self, other: V) -> bool
58    where
59        V: AsRef<Version>,
60    {
61        use std::cmp::Ordering;
62
63        let other = other.as_ref();
64
65        matches!(
66            (
67                self.major.cmp(&other.major),
68                self.minor.cmp(&other.minor),
69                self.patch.cmp(&other.patch),
70            ),
71            (Ordering::Greater, _, _)
72                | (Ordering::Equal, Ordering::Greater, _)
73                | (Ordering::Equal, Ordering::Equal, Ordering::Greater)
74        )
75    }
76}
77
78#[derive(Debug, Deserialize)]
79struct CrateResponse {
80    #[serde(rename = "crate")]
81    crate_: Crate,
82}
83
84#[derive(Debug, Deserialize)]
85struct Crate {
86    max_version: String,
87}
88
89pub async fn latest_crate_version<S: Display, U: Display>(
90    crate_name: S,
91    user_agent: U,
92) -> Result<Version> {
93    let url = format!("https://crates.io/api/v1/crates/{crate_name}");
94    let response = http::Request::new(url)
95        .with_user_agent(user_agent.to_string())
96        .get_json::<CrateResponse>()
97        .await?;
98    response.crate_.max_version.parse()
99}
100
101#[cfg(not(target_arch = "wasm32"))]
102pub mod blocking {
103    use super::*;
104    use reqwest::blocking::Client;
105    use reqwest::header::*;
106
107    pub fn latest_crate_version<S: Display, U: Display>(
108        crate_name: S,
109        user_agent: U,
110    ) -> Result<Version> {
111        let url = format!("https://crates.io/api/v1/crates/{crate_name}");
112        let mut headers = HeaderMap::new();
113        headers.insert(
114            USER_AGENT,
115            HeaderValue::from_str(user_agent.to_string().as_str())?,
116        );
117        let response = Client::builder()
118            .default_headers(headers)
119            .build()?
120            .get(url)
121            .send()?
122            .json::<CrateResponse>()?;
123        response.crate_.max_version.parse()
124    }
125}