cargo_edit/
registry.rs

1use super::errors::*;
2use std::collections::HashMap;
3use std::path::Path;
4use url::Url;
5
6const CRATES_IO_INDEX: &str = tame_index::index::sparse::CRATES_IO_HTTP_INDEX;
7const CRATES_IO_REGISTRY: &str = "crates-io";
8
9/// Find the URL of a registry
10pub fn registry_url(manifest_path: &Path, registry: Option<&str>) -> CargoResult<Url> {
11    // TODO support local registry sources, directory sources, git sources: https://doc.rust-lang.org/cargo/reference/source-replacement.html?highlight=replace-with#source-replacement
12    fn read_config(
13        registries: &mut HashMap<String, Source>,
14        path: impl AsRef<Path>,
15    ) -> CargoResult<()> {
16        let path = path.as_ref();
17        // TODO unit test for source replacement
18        let content = std::fs::read_to_string(path)?;
19        let config = toml::from_str::<CargoConfig>(&content)
20            .with_context(|| anyhow::format_err!("invalid cargo config at {}", path.display()))?;
21        for (key, value) in config.registries {
22            registries.entry(key).or_insert(Source {
23                registry: value.index,
24                replace_with: None,
25            });
26        }
27        for (key, value) in config.source {
28            registries.entry(key).or_insert(value);
29        }
30        Ok(())
31    }
32    // registry might be replaced with another source
33    // it's looks like a singly linked list
34    // put relations in this map.
35    let mut registries: HashMap<String, Source> = HashMap::new();
36    // ref: https://doc.rust-lang.org/cargo/reference/config.html#hierarchical-structure
37    for work_dir in manifest_path
38        .parent()
39        .expect("there must be a parent directory")
40        .ancestors()
41    {
42        let work_cargo_dir = work_dir.join(".cargo");
43        let config_path = work_cargo_dir.join("config");
44        if config_path.is_file() {
45            read_config(&mut registries, config_path)?;
46        } else {
47            let config_path = work_cargo_dir.join("config.toml");
48            if config_path.is_file() {
49                read_config(&mut registries, config_path)?;
50            }
51        }
52    }
53
54    let default_cargo_home = home::cargo_home()?;
55    let default_config_path = default_cargo_home.join("config");
56    if default_config_path.is_file() {
57        read_config(&mut registries, default_config_path)?;
58    } else {
59        let default_config_path = default_cargo_home.join("config.toml");
60        if default_config_path.is_file() {
61            read_config(&mut registries, default_config_path)?;
62        }
63    }
64
65    // find head of the relevant linked list
66    let mut source = match registry {
67        Some(CRATES_IO_INDEX) | None => {
68            let mut source = registries.remove(CRATES_IO_REGISTRY).unwrap_or_default();
69            source
70                .registry
71                .get_or_insert_with(|| CRATES_IO_INDEX.to_string());
72            source
73        }
74        Some(r) => registries
75            .remove(r)
76            .with_context(|| anyhow::format_err!("The registry '{}' could not be found", r))?,
77    };
78
79    // search this linked list and find the tail
80    while let Some(replace_with) = &source.replace_with {
81        let is_crates_io = replace_with == CRATES_IO_INDEX;
82        source = registries.remove(replace_with).with_context(|| {
83            anyhow::format_err!("The source '{}' could not be found", replace_with)
84        })?;
85        if is_crates_io {
86            source
87                .registry
88                .get_or_insert_with(|| CRATES_IO_INDEX.to_string());
89        }
90    }
91
92    let registry_url = source
93        .registry
94        .ok_or_else(|| anyhow::format_err!("missing `registry`"))?;
95    let registry_url = Url::parse(&registry_url)
96        .with_context(|| anyhow::format_err!("invalid `registry` field"))?;
97
98    Ok(registry_url)
99}
100
101#[derive(Debug, Deserialize)]
102struct CargoConfig {
103    #[serde(default)]
104    registries: HashMap<String, Registry>,
105    #[serde(default)]
106    source: HashMap<String, Source>,
107}
108
109#[derive(Default, Debug, Deserialize)]
110struct Source {
111    #[serde(rename = "replace-with")]
112    replace_with: Option<String>,
113    registry: Option<String>,
114}
115
116#[derive(Debug, Deserialize)]
117struct Registry {
118    index: Option<String>,
119}
120
121mod code_from_cargo {
122    #![allow(dead_code)]
123
124    #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
125    pub enum Kind {
126        Git(GitReference),
127        Path,
128        Registry,
129        LocalRegistry,
130        Directory,
131    }
132
133    #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
134    pub enum GitReference {
135        Tag(String),
136        Branch(String),
137        Rev(String),
138        DefaultBranch,
139    }
140}