use std::env;
use std::path::PathBuf;
use std::sync::OnceLock;
pub const DEFAULT_SPARSE_INDEX: &str = "https://index.crates.io";
pub fn sparse_index_base(override_url: Option<&str>) -> String {
if let Some(u) = override_url {
return normalize(u);
}
static CACHE: OnceLock<String> = OnceLock::new();
CACHE
.get_or_init(|| resolve_from_config().unwrap_or_else(|| DEFAULT_SPARSE_INDEX.to_string()))
.clone()
}
fn normalize(url: &str) -> String {
url.trim().trim_end_matches('/').to_string()
}
pub(crate) fn cargo_home() -> Option<PathBuf> {
if let Ok(s) = env::var("CARGO_HOME") {
if !s.is_empty() {
return Some(PathBuf::from(s));
}
}
env::var("HOME").ok().map(|h| PathBuf::from(h).join(".cargo"))
}
fn resolve_from_config() -> Option<String> {
let path = cargo_home()?.join("config.toml");
let body = std::fs::read_to_string(&path).ok()?;
parse_sparse_base(&body)
}
pub fn parse_sparse_base(body: &str) -> Option<String> {
let value: toml::Value = toml::from_str(body).ok()?;
let sources = value.get("source")?.as_table()?;
let crates_io = sources.get("crates-io")?.as_table()?;
let replace_with = crates_io.get("replace-with")?.as_str()?;
let target = sources.get(replace_with)?.as_table()?;
if let Some(reg) = target.get("registry").and_then(|v| v.as_str()) {
if let Some(rest) = reg.strip_prefix("sparse+") {
return Some(normalize(rest));
}
return None;
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_ustc_style_mirror() {
let body = r#"
[source.crates-io]
replace-with = "ustc"
[source.ustc]
registry = "sparse+https://mirrors.ustc.edu.cn/crates.io-index/"
"#;
assert_eq!(
parse_sparse_base(body).as_deref(),
Some("https://mirrors.ustc.edu.cn/crates.io-index")
);
}
#[test]
fn returns_none_for_git_mirror() {
let body = r#"
[source.crates-io]
replace-with = "tuna"
[source.tuna]
registry = "https://mirrors.tuna.tsinghua.edu.cn/git/crates.io-index.git"
"#;
assert_eq!(parse_sparse_base(body), None);
}
#[test]
fn returns_none_when_no_replace_with() {
let body = r#"
[net]
retry = 3
"#;
assert_eq!(parse_sparse_base(body), None);
}
#[test]
fn returns_none_when_target_source_missing() {
let body = r#"
[source.crates-io]
replace-with = "ghost"
"#;
assert_eq!(parse_sparse_base(body), None);
}
#[test]
fn normalize_strips_trailing_slash() {
assert_eq!(normalize("https://example.com/"), "https://example.com");
assert_eq!(normalize(" https://x.com "), "https://x.com");
}
#[test]
fn override_takes_precedence() {
let s = sparse_index_base(Some("https://custom.example/"));
assert_eq!(s, "https://custom.example");
}
#[test]
fn default_when_nothing_configured() {
assert!(DEFAULT_SPARSE_INDEX.starts_with("https://"));
}
}