Skip to main content

normalize_package_index/index/
go.rs

1//! Go module index fetcher.
2//!
3//! Fetches package metadata from pkg.go.dev and proxy.golang.org.
4//!
5//! ## API Strategy
6//! - **fetch**: `proxy.golang.org/{module}/@v/list` + `@latest` - Official Go proxy
7//! - **fetch_versions**: `proxy.golang.org/{module}/@v/list` - version list
8//! - **search**: Not supported (Go has no search API, use pkg.go.dev website)
9//! - **fetch_all**: Not supported (decentralized registry)
10
11use super::{IndexError, PackageIndex, PackageMeta, VersionMeta};
12
13/// Go module index fetcher.
14pub struct Go;
15
16impl Go {
17    /// Go proxy API.
18    const GO_PROXY: &'static str = "https://proxy.golang.org";
19
20    /// pkg.go.dev API (for metadata).
21    const PKG_GO_DEV: &'static str = "https://pkg.go.dev";
22}
23
24impl PackageIndex for Go {
25    fn ecosystem(&self) -> &'static str {
26        "go"
27    }
28
29    fn display_name(&self) -> &'static str {
30        "Go Modules"
31    }
32
33    fn fetch(&self, name: &str) -> Result<PackageMeta, IndexError> {
34        // Get latest version from proxy
35        let versions = self.fetch_versions(name)?;
36        let latest = versions
37            .first()
38            .ok_or_else(|| IndexError::NotFound(name.to_string()))?;
39
40        // Go modules are typically hosted on GitHub
41        let repository = if name.starts_with("github.com/") {
42            Some(format!("https://{}", name))
43        } else if name.starts_with("golang.org/x/") {
44            // Standard library extensions
45            let pkg_name = name.strip_prefix("golang.org/x/").unwrap_or(name);
46            Some(format!("https://github.com/golang/{}", pkg_name))
47        } else {
48            None
49        };
50
51        // Get version info for release time
52        let info_url = format!("{}/{}/@v/{}.info", Self::GO_PROXY, name, latest.version);
53        let published = ureq::get(&info_url)
54            .call()
55            .ok()
56            .and_then(|r| r.into_json::<serde_json::Value>().ok())
57            .and_then(|v| v["Time"].as_str().map(String::from));
58
59        Ok(PackageMeta {
60            name: name.to_string(),
61            version: latest.version.clone(),
62            description: None, // Would need to scrape pkg.go.dev
63            homepage: Some(format!("{}/{}", Self::PKG_GO_DEV, name)),
64            repository,
65            license: None, // Would need to scrape pkg.go.dev
66            binaries: Vec::new(),
67            keywords: Vec::new(),
68            maintainers: Vec::new(),
69            published,
70            downloads: None, // Go proxy doesn't track downloads
71            archive_url: Some(format!(
72                "{}/{}/@v/{}.zip",
73                Self::GO_PROXY,
74                name,
75                latest.version
76            )),
77            checksum: None, // Would need to parse go.sum format
78            extra: Default::default(),
79        })
80    }
81
82    fn fetch_versions(&self, name: &str) -> Result<Vec<VersionMeta>, IndexError> {
83        let url = format!("{}/{}/@v/list", Self::GO_PROXY, name);
84        let response = ureq::get(&url).call()?.into_string()?;
85
86        let mut versions: Vec<VersionMeta> = response
87            .lines()
88            .filter(|line| !line.is_empty())
89            .map(|version| VersionMeta {
90                version: version.to_string(),
91                released: None,
92                yanked: false,
93            })
94            .collect();
95
96        // Sort by semver (newest first) - simplified sorting
97        versions.sort_by(|a, b| b.version.cmp(&a.version));
98
99        if versions.is_empty() {
100            return Err(IndexError::NotFound(name.to_string()));
101        }
102
103        Ok(versions)
104    }
105
106    fn search(&self, query: &str) -> Result<Vec<PackageMeta>, IndexError> {
107        // Go doesn't have a search API in the proxy
108        // Would need to scrape pkg.go.dev or use a third-party index
109        Err(IndexError::Network(format!(
110            "Go module search not available via API. Visit: {}/search?q={}",
111            Self::PKG_GO_DEV,
112            query
113        )))
114    }
115}