use std::path::PathBuf;
use super::{AnyRegistry, LocalRegistry, OverlayRegistry, RemoteRegistry};
#[derive(Debug, Clone)]
pub enum RegistrySpec {
Local { path: Option<PathBuf> },
Remote { api_url: String, index_url: String },
}
impl std::fmt::Display for RegistrySpec {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RegistrySpec::Local { path: None } => write!(f, "local"),
RegistrySpec::Local { path: Some(p) } => write!(f, "local={}", p.display()),
RegistrySpec::Remote { api_url, index_url } if api_url == index_url => {
write!(f, "remote={}", api_url)
}
RegistrySpec::Remote { api_url, index_url }
if api_url == "https://crates.io" && index_url == "https://index.crates.io" =>
{
write!(f, "crates.io")
}
RegistrySpec::Remote { api_url, index_url } => {
write!(f, "remote={},{}", api_url, index_url)
}
}
}
}
impl RegistrySpec {
pub fn crates_io() -> Self {
RegistrySpec::Remote {
api_url: "https://crates.io".to_string(),
index_url: "https://index.crates.io".to_string(),
}
}
pub fn local(path: impl Into<PathBuf>) -> Self {
RegistrySpec::Local {
path: Some(path.into()),
}
}
pub fn local_temp() -> Self {
RegistrySpec::Local { path: None }
}
pub fn remote(api_url: impl Into<String>, index_url: impl Into<String>) -> Self {
RegistrySpec::Remote {
api_url: api_url.into(),
index_url: index_url.into(),
}
}
}
impl std::str::FromStr for RegistrySpec {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s == "crates.io" || s == "crates-io" {
return Ok(RegistrySpec::crates_io());
}
let (reg_type, value) = if let Some(idx) = s.find('=') {
(&s[..idx], Some(&s[idx + 1..]))
} else {
(s, None)
};
match reg_type {
"local" => {
let path = value.map(PathBuf::from);
Ok(RegistrySpec::Local { path })
}
"remote" => {
let value = value.ok_or("remote registry requires a URL")?;
let (api_url, index_url) = if let Some(idx) = value.find(',') {
(value[..idx].to_string(), value[idx + 1..].to_string())
} else {
(value.to_string(), value.to_string())
};
Ok(RegistrySpec::Remote { api_url, index_url })
}
_ => Err(format!(
"unknown registry type '{}'. Use: local, remote, or crates.io",
reg_type
)),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct RegistryBuildOptions {
pub permissive_publishing: bool,
pub read_only: bool,
}
pub struct BuiltRegistry {
pub registry: AnyRegistry,
pub upstream_hosts: Vec<String>,
pub temp_dirs: Vec<tempfile::TempDir>,
}
impl BuiltRegistry {
pub fn upstream_api(&self, specs: &[RegistrySpec]) -> String {
for spec in specs.iter().rev() {
if let RegistrySpec::Remote { api_url, .. } = spec {
return api_url.clone();
}
}
"https://crates.io".to_string()
}
}
pub fn build_registry(specs: &[RegistrySpec], options: &RegistryBuildOptions) -> BuiltRegistry {
let mut upstream_hosts = Vec::new();
let mut temp_dirs = Vec::new();
let writable_idx = if options.read_only { None } else { Some(0) };
let mut registry: Option<AnyRegistry> = None;
for (idx, spec) in specs.iter().enumerate().rev() {
let layer: AnyRegistry = match spec {
RegistrySpec::Local { path } => {
let path = path.clone().unwrap_or_else(|| {
let temp_dir =
tempfile::tempdir().expect("Failed to create temporary directory");
let path = temp_dir.path().to_path_buf();
temp_dirs.push(temp_dir);
path
});
std::fs::create_dir_all(path.join("index")).ok();
if writable_idx == Some(idx) {
let validate = !options.permissive_publishing;
AnyRegistry::new(LocalRegistry::new(path, validate))
} else {
AnyRegistry::new(LocalRegistry::read_only(path))
}
}
RegistrySpec::Remote { api_url, index_url } => {
if let Ok(url) = url::Url::parse(api_url)
&& let Some(host) = url.host_str()
&& !upstream_hosts.contains(&host.to_string())
{
upstream_hosts.push(host.to_string());
}
if let Ok(url) = url::Url::parse(index_url)
&& let Some(host) = url.host_str()
&& !upstream_hosts.contains(&host.to_string())
{
upstream_hosts.push(host.to_string());
}
if writable_idx == Some(idx) {
AnyRegistry::new(RemoteRegistry::writable(index_url.clone(), api_url.clone()))
} else {
AnyRegistry::new(RemoteRegistry::new(index_url.clone(), api_url.clone()))
}
}
};
registry = Some(match registry {
None => layer,
Some(bottom) => AnyRegistry::new(OverlayRegistry::new(layer, bottom)),
});
}
if upstream_hosts.iter().any(|h| h.contains("crates.io"))
&& !upstream_hosts.contains(&"static.crates.io".to_string())
{
upstream_hosts.push("static.crates.io".to_string());
}
BuiltRegistry {
registry: registry.expect("At least one registry must be specified"),
upstream_hosts,
temp_dirs,
}
}