facto_core/registries/
go.rs1use 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 Err(RegistryError::NotSupported)
118 })
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 #[tokio::test]
127 #[ignore] 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}