Skip to main content

facto_core/registries/
go.rs

1use std::future::Future;
2use std::pin::Pin;
3
4use crate::models::*;
5use crate::registries::{Registry, RegistryError, RegistryResult};
6
7pub struct GoModules {
8    client: reqwest::Client,
9}
10
11impl GoModules {
12    pub fn new(client: reqwest::Client) -> Self {
13        Self { client }
14    }
15}
16
17#[derive(serde::Deserialize)]
18struct GoLatestResponse {
19    #[serde(rename = "Version")]
20    version: Option<String>,
21    #[serde(rename = "Time")]
22    time: Option<String>,
23}
24
25impl Registry for GoModules {
26    fn id(&self) -> &str {
27        "go"
28    }
29
30    fn display_name(&self) -> &str {
31        "Go Modules"
32    }
33
34    fn get_package<'a>(
35        &'a self,
36        name: &'a str,
37    ) -> Pin<Box<dyn Future<Output = RegistryResult<PackageInfo>> + Send + 'a>> {
38        Box::pin(async move {
39            let url = format!(
40                "https://proxy.golang.org/{}/@latest",
41                urlencoding::encode(name)
42            );
43            let resp = self.client.get(&url).send().await?;
44
45            match resp.status().as_u16() {
46                404 | 410 => return Err(RegistryError::NotFound),
47                429 => return Err(RegistryError::RateLimited),
48                code if !(200..300).contains(&code) => {
49                    return Err(RegistryError::Parse(format!("unexpected status {code}")));
50                }
51                _ => {}
52            }
53
54            let data: GoLatestResponse = crate::registries::bounded_json(resp).await?;
55            let updated_at = data.time.as_ref().and_then(|ts| ts.parse().ok());
56
57            Ok(PackageInfo {
58                name: name.to_string(),
59                registry: "go".into(),
60                latest_version: data.version,
61                description: None,
62                license: None,
63                homepage: None,
64                repository: Some(format!("https://{}", name)),
65                authors: Vec::new(),
66                updated_at,
67                keywords: Vec::new(),
68                classifiers: Vec::new(),
69                requires_python: None,
70            })
71        })
72    }
73
74    fn get_versions<'a>(
75        &'a self,
76        name: &'a str,
77    ) -> Pin<Box<dyn Future<Output = RegistryResult<Vec<VersionInfo>>> + Send + 'a>> {
78        Box::pin(async move {
79            let url = format!(
80                "https://proxy.golang.org/{}/@v/list",
81                urlencoding::encode(name)
82            );
83            let resp = self.client.get(&url).send().await?;
84
85            match resp.status().as_u16() {
86                404 | 410 => return Err(RegistryError::NotFound),
87                429 => return Err(RegistryError::RateLimited),
88                code if !(200..300).contains(&code) => {
89                    return Err(RegistryError::Parse(format!("unexpected status {code}")));
90                }
91                _ => {}
92            }
93
94            let text = crate::registries::bounded_text(resp).await?;
95            let mut versions: Vec<VersionInfo> = text
96                .lines()
97                .filter(|l| !l.is_empty())
98                .map(|version| VersionInfo {
99                    version: version.to_string(),
100                    released_at: None,
101                    prerelease: crate::registries::is_prerelease(version),
102                })
103                .collect();
104
105            crate::registries::sort_versions_semver(&mut versions);
106            Ok(versions)
107        })
108    }
109
110    fn search<'a>(
111        &'a self,
112        _query: &'a str,
113        _limit: usize,
114    ) -> Pin<Box<dyn Future<Output = RegistryResult<Vec<SearchResult>>> + Send + 'a>> {
115        Box::pin(async {
116            // Go module proxy has no search API.
117            Err(RegistryError::NotSupported)
118        })
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[tokio::test]
127    #[ignore] // requires network
128    async fn test_get_package() {
129        let go = GoModules::new(crate::http::default_client().unwrap());
130        let pkg = go.get_package("golang.org/x/net").await.unwrap();
131        assert_eq!(pkg.registry, "go");
132        assert!(pkg.latest_version.is_some());
133    }
134}