aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'crates/repos/src/go.rs')
-rw-r--r--crates/repos/src/go.rs518+0 −518
1 files changed, 0 insertions, 518 deletions
diff --git a/crates/repos/src/go.rs b/crates/repos/src/go.rs
deleted file mode 100644
--- a/crates/repos/src/go.rs
+++ /dev/null
@@ -1,518 +0,0 @@
-// SPDX-FileCopyrightText: 2026 Nikolay Govorov
-// SPDX-License-Identifier: AGPL-3.0-or-later
-
-use std::time::Duration;
-
-use serde::{Deserialize, Serialize};
-use url::Url;
-
-use super::*;
-
-#[derive(Debug, Clone, Deserialize)]
-#[serde(default)]
-pub struct GoConfig {
- pub enabled: bool,
- pub upstream: Url,
- pub refresh_interval: u64,
-}
-
-impl Default for GoConfig {
- fn default() -> Self {
- Self {
- enabled: true,
- upstream: Url::parse("https://go.dev/dl/").unwrap(),
- refresh_interval: 60 * 60,
- }
- }
-}
-
-impl BackendConfig for GoConfig {
- fn enabled(&self) -> bool {
- self.enabled
- }
- fn refresh_interval(&self) -> Duration {
- Duration::from_secs(self.refresh_interval)
- }
-}
-
-/// Typed metadata for Go files
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct GoFileMeta {
- pub kind: FileKind,
-}
-
-/// Typed metadata for Go releases
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct GoReleaseMeta {
- pub stable: bool,
-}
-
-/// Upstream API file format (only used for parsing JSON from go.dev)
-#[derive(Deserialize)]
-struct UpstreamFile {
- filename: String,
- os: Option<String>,
- arch: Option<String>,
- sha256: String,
- size: u64,
- kind: FileKind,
-}
-
-/// Upstream API release format (only used for parsing JSON from go.dev)
-#[derive(Deserialize)]
-struct UpstreamRelease {
- version: String,
- stable: bool,
- files: Vec<UpstreamFile>,
-}
-
-/// Go backend specification
-pub struct GoSpec;
-
-#[async_trait::async_trait]
-impl BackendSpec for GoSpec {
- const ID: &'static str = "go";
-
- type Config = GoConfig;
- type ReleaseMeta = GoReleaseMeta;
- type FileMeta = GoFileMeta;
- type Filename<'a> = GoFilename<'a>;
-
- async fn fetch_index(
- config: &Self::Config,
- network: &dyn BackendNetwork,
- ) -> Result<Vec<(RawRelease, Vec<RawReleaseFile>)>, BackendError> {
- let mut url = config.upstream.clone();
- url.query_pairs_mut()
- .append_pair("mode", "json")
- .append_pair("include", "all");
-
- let bytes = network.http_get(&url).await?;
- let upstream: Vec<UpstreamRelease> =
- serde_json::from_slice(&bytes).map_err(|e| BackendError::Upstream(e.to_string()))?;
-
- let mut releases = Vec::with_capacity(upstream.len());
- for u in upstream {
- let sort_key = match GoVersion::parse(&u.version) {
- Ok(v) => v.sort_key(),
- Err(_) => {
- tracing::error!(version = u.version, "invalid go version, skipping");
- continue;
- }
- };
-
- let release: Release<GoReleaseMeta> = Release {
- backend: Self::ID.to_string(),
- version: u.version.clone(),
- sort_key,
- meta: GoReleaseMeta { stable: u.stable },
- };
-
- let files: Vec<RawReleaseFile> = u
- .files
- .iter()
- .map(|f| {
- let file: ReleaseFile<GoFileMeta> = ReleaseFile {
- backend: Self::ID.to_string(),
- version: u.version.clone(),
- filename: f.filename.clone(),
- checksum: f.sha256.clone(),
- size: f.size as i64,
- os: f.os.as_ref().and_then(|s| s.parse().ok()),
- arch: f.arch.as_ref().and_then(|s| s.parse().ok()),
- meta: GoFileMeta { kind: f.kind },
- };
- file.to_raw()
- })
- .collect();
-
- releases.push((release.to_raw(), files));
- }
-
- Ok(releases)
- }
-}
-
-/// Type alias for Go backend
-pub type GoBackend = Backend<GoSpec>;
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-enum ReleaseType {
- Stable,
- ReleaseCandidate(u64),
- Beta(u64),
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct GoVersion {
- major: u64,
- minor: u64,
- patch: Option<u64>,
- release_type: ReleaseType,
-}
-
-impl GoVersion {
- /// Parses version string like "go1", "go1.22.3", "go1.23rc1", or "go1.9.2rc2".
- fn parse(s: &str) -> Result<Self, BackendError> {
- let s = s.strip_prefix("go").ok_or(BackendError::NotFound)?;
- let parts: Vec<&str> = s.split('.').collect();
- let (version, consumed) = Self::from_parts(&parts)?;
- if consumed != parts.len() {
- return Err(BackendError::NotFound);
- }
- Ok(version)
- }
-
- /// Parses version from dot-separated parts, returns (version, parts_consumed).
- fn from_parts(parts: &[&str]) -> Result<(Self, usize), BackendError> {
- let major = parts
- .first()
- .ok_or(BackendError::NotFound)?
- .parse()
- .map_err(|_| BackendError::NotFound)?;
-
- let Some(minor_str) = parts.get(1) else {
- return Ok((
- Self {
- major,
- minor: 0,
- patch: None,
- release_type: ReleaseType::Stable,
- },
- 1,
- ));
- };
- let (minor, minor_release) = Self::parse_minor_with_release(minor_str)?;
-
- let Some(patch_str) = parts.get(2) else {
- return Ok((
- Self {
- major,
- minor,
- patch: None,
- release_type: minor_release,
- },
- 2,
- ));
- };
- if let Ok((p, patch_release)) = Self::parse_version_part(patch_str) {
- Ok((
- Self {
- major,
- minor,
- patch: Some(p),
- release_type: patch_release.unwrap_or(minor_release),
- },
- 3,
- ))
- } else {
- Ok((
- Self {
- major,
- minor,
- patch: None,
- release_type: minor_release,
- },
- 2,
- ))
- }
- }
-
- fn sort_key(&self) -> i64 {
- let vtype = match self.release_type {
- ReleaseType::Beta(n) => super::VersionType::Beta(n),
- ReleaseType::ReleaseCandidate(n) => super::VersionType::Rc(n),
- ReleaseType::Stable => super::VersionType::Stable,
- };
- super::stable_version(self.major, self.minor, self.patch.unwrap_or(0), vtype)
- }
-
- /// Parses a version part (number with optional rc/beta suffix).
- /// "25" -> (25, None)
- /// "2rc2" -> (2, Some(ReleaseCandidate(2)))
- /// "1beta1" -> (1, Some(Beta(1)))
- fn parse_version_part(s: &str) -> Result<(u64, Option<ReleaseType>), BackendError> {
- if let Some(idx) = s.find("rc") {
- let num = s[..idx]
- .parse::<u64>()
- .map_err(|_| BackendError::NotFound)?;
- let rc_num = s[idx + 2..]
- .parse::<u64>()
- .map_err(|_| BackendError::NotFound)?;
- return Ok((num, Some(ReleaseType::ReleaseCandidate(rc_num))));
- }
- if let Some(idx) = s.find("beta") {
- let num = s[..idx]
- .parse::<u64>()
- .map_err(|_| BackendError::NotFound)?;
- let beta_num = s[idx + 4..]
- .parse::<u64>()
- .map_err(|_| BackendError::NotFound)?;
- return Ok((num, Some(ReleaseType::Beta(beta_num))));
- }
- let num = s.parse::<u64>().map_err(|_| BackendError::NotFound)?;
- Ok((num, None))
- }
-
- /// Parses minor version with possible release type suffix.
- /// "25" -> (25, Stable)
- /// "26rc2" -> (26, ReleaseCandidate(2))
- /// "26beta1" -> (26, Beta(1))
- fn parse_minor_with_release(s: &str) -> Result<(u64, ReleaseType), BackendError> {
- let (num, release) = GoVersion::parse_version_part(s)?;
- Ok((num, release.unwrap_or(ReleaseType::Stable)))
- }
-}
-
-#[cfg(test)]
-mod tests_go_version {
- use super::*;
-
- #[test]
- fn test_parse_go1() {
- let v = GoVersion::parse("go1").unwrap();
- assert_eq!(v.major, 1);
- assert_eq!(v.minor, 0);
- assert_eq!(v.patch, None);
- assert!(matches!(v.release_type, ReleaseType::Stable));
- }
-
- #[test]
- fn test_parse_patch_rc() {
- let v = GoVersion::parse("go1.9.2rc2").unwrap();
- assert_eq!(v.major, 1);
- assert_eq!(v.minor, 9);
- assert_eq!(v.patch, Some(2));
- assert!(matches!(v.release_type, ReleaseType::ReleaseCandidate(2)));
- }
-
- #[test]
- fn test_no_collision_go19_and_go192rc2() {
- let v1 = GoVersion::parse("go1.9").unwrap();
- let v2 = GoVersion::parse("go1.9.2rc2").unwrap();
- assert_ne!(v1.sort_key(), v2.sort_key());
- // go1.9 (stable) should sort before go1.9.2rc2
- assert!(v1.sort_key() < v2.sort_key());
- }
-}
-
-/// Describes a single file stored at `dl.google.com/go/`.
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct GoFilename<'a> {
- filename: &'a str,
- version: GoVersion,
- os: Option<&'a str>,
- arch: Option<&'a str>,
- kind: FileKind,
- archive: Archive,
- sha256: bool,
-}
-
-impl<'a> BackendFilename<'a, GoConfig> for GoFilename<'a> {
- fn parse(filename: &'a str) -> Result<Self, BackendError> {
- let mut buffer = filename;
- let mut sha256 = false;
- let archive;
-
- // Strip "go" prefix
- buffer = buffer.strip_prefix("go").ok_or(BackendError::NotFound)?;
-
- // Check for .sha256 suffix
- if let Some(it) = buffer.strip_suffix(".sha256") {
- buffer = it;
- sha256 = true;
- }
-
- // Determine archive type
- if let Some(it) = buffer.strip_suffix(".tar.gz") {
- buffer = it;
- archive = Archive::TarGz;
- } else if let Some(it) = buffer.strip_suffix(".zip") {
- buffer = it;
- archive = Archive::Zip;
- } else if let Some(it) = buffer.strip_suffix(".msi") {
- buffer = it;
- archive = Archive::Msi;
- } else if let Some(it) = buffer.strip_suffix(".pkg") {
- buffer = it;
- archive = Archive::Pkg;
- } else {
- return Err(BackendError::NotFound);
- }
-
- if buffer.is_empty() {
- return Err(BackendError::NotFound);
- }
-
- // Split by dots: "1.25.6.linux-amd64" -> ["1", "25", "6", "linux-amd64"]
- let parts: Vec<&str> = buffer.split('.').collect();
- if parts.len() < 3 {
- return Err(BackendError::NotFound);
- }
-
- // Parse version, get how many parts were consumed
- let (version, consumed) = GoVersion::from_parts(&parts)?;
-
- // Remainder must exist and be either "src" or "os-arch"
- let remainder = parts.get(consumed).ok_or(BackendError::NotFound)?;
- let (os, arch, kind) = if *remainder == "src" {
- (None, None, FileKind::Source)
- } else if let Some((os, arch)) = remainder.split_once('-') {
- let kind = match archive {
- Archive::Msi | Archive::Pkg => FileKind::Installer,
- _ => FileKind::Archive,
- };
- (Some(os), Some(arch), kind)
- } else {
- return Err(BackendError::NotFound);
- };
-
- Ok(GoFilename {
- filename,
- version,
- os,
- arch,
- kind,
- archive,
- sha256,
- })
- }
-
- fn upstream_url(&self, config: &GoConfig, source: &str) -> Result<Url, BackendError> {
- let mut url = config.upstream.clone();
- url.path_segments_mut()
- .map_err(|()| BackendError::Internal("cannot build upstream URL".into()))?
- .pop_if_empty()
- .push(self.filename);
- url.query_pairs_mut().append_pair("source", source);
- Ok(url)
- }
-
- fn is_sha256(&self) -> bool {
- self.sha256
- }
-}
-
-#[cfg(test)]
-mod tests_go_filename {
- use super::*;
-
- #[test]
- fn test_parse_stable_binary() {
- let t = GoFilename::parse("go1.25.6.linux-amd64.tar.gz").unwrap();
- assert_eq!(t.version.major, 1);
- assert_eq!(t.version.minor, 25);
- assert_eq!(t.version.patch, Some(6));
- assert!(matches!(t.version.release_type, ReleaseType::Stable));
- assert_eq!(t.os, Some("linux"));
- assert_eq!(t.arch, Some("amd64"));
- assert!(matches!(t.kind, FileKind::Archive));
- assert_eq!(t.archive, Archive::TarGz);
- assert!(!t.sha256);
- }
-
- #[test]
- fn test_parse_first_minor_release() {
- let t = GoFilename::parse("go1.25.linux-amd64.tar.gz").unwrap();
- assert_eq!(t.version.major, 1);
- assert_eq!(t.version.minor, 25);
- assert_eq!(t.version.patch, None);
- assert!(matches!(t.version.release_type, ReleaseType::Stable));
- }
-
- #[test]
- fn test_parse_rc() {
- let t = GoFilename::parse("go1.26rc2.linux-amd64.tar.gz").unwrap();
- assert_eq!(t.version.major, 1);
- assert_eq!(t.version.minor, 26);
- assert_eq!(t.version.patch, None);
- assert!(matches!(
- t.version.release_type,
- ReleaseType::ReleaseCandidate(2)
- ));
- }
-
- #[test]
- fn test_parse_beta() {
- let t = GoFilename::parse("go1.26beta1.darwin-arm64.tar.gz").unwrap();
- assert_eq!(t.version.major, 1);
- assert_eq!(t.version.minor, 26);
- assert!(matches!(t.version.release_type, ReleaseType::Beta(1)));
- }
-
- #[test]
- fn test_parse_source() {
- let t = GoFilename::parse("go1.25.6.src.tar.gz").unwrap();
- assert!(matches!(t.kind, FileKind::Source));
- assert_eq!(t.os, None);
- assert_eq!(t.arch, None);
- assert_eq!(t.version.patch, Some(6));
- }
-
- #[test]
- fn test_parse_windows_zip() {
- let t = GoFilename::parse("go1.25.6.windows-amd64.zip").unwrap();
- assert_eq!(t.archive, Archive::Zip);
- assert_eq!(t.os, Some("windows"));
- assert_eq!(t.arch, Some("amd64"));
- assert!(matches!(t.kind, FileKind::Archive));
- }
-
- #[test]
- fn test_parse_msi() {
- let t = GoFilename::parse("go1.25.6.windows-amd64.msi").unwrap();
- assert_eq!(t.archive, Archive::Msi);
- assert!(matches!(t.kind, FileKind::Installer));
- }
-
- #[test]
- fn test_parse_pkg() {
- let t = GoFilename::parse("go1.25.6.darwin-arm64.pkg").unwrap();
- assert_eq!(t.archive, Archive::Pkg);
- assert!(matches!(t.kind, FileKind::Installer));
- }
-
- #[test]
- fn test_parse_sha256() {
- let t = GoFilename::parse("go1.25.6.linux-amd64.tar.gz.sha256").unwrap();
- assert!(t.sha256);
- assert_eq!(t.archive, Archive::TarGz);
- assert_eq!(t.version.patch, Some(6));
- }
-
- #[test]
- fn test_upstream_url() {
- let t = GoFilename::parse("go1.25.6.linux-amd64.tar.gz").unwrap();
- let config = GoConfig {
- upstream: Url::parse("https://dl.google.com/go/").unwrap(),
- ..Default::default()
- };
- let url = t.upstream_url(&config, "tesor:test").unwrap();
- assert_eq!(
- url.as_str(),
- "https://dl.google.com/go/go1.25.6.linux-amd64.tar.gz?source=tesor%3Atest"
- );
- }
-
- #[test]
- fn test_parse_patch_rc() {
- let t = GoFilename::parse("go1.9.2rc2.linux-amd64.tar.gz").unwrap();
- assert_eq!(t.version.major, 1);
- assert_eq!(t.version.minor, 9);
- assert_eq!(t.version.patch, Some(2));
- assert!(matches!(
- t.version.release_type,
- ReleaseType::ReleaseCandidate(2)
- ));
- }
-
- #[test]
- fn test_invalid_prefix() {
- assert!(GoFilename::parse("rust1.25.6.linux-amd64.tar.gz").is_err());
- }
-
- #[test]
- fn test_invalid_extension() {
- assert!(GoFilename::parse("go1.25.6.linux-amd64.tar.bz2").is_err());
- }
-}