• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! Tools for parsing [Cargo configuration](https://doc.rust-lang.org/cargo/reference/config.html) files
2 
3 use std::collections::BTreeMap;
4 use std::fs;
5 use std::path::Path;
6 use std::str::FromStr;
7 
8 use crate::utils;
9 use anyhow::{bail, Result};
10 use serde::{Deserialize, Serialize};
11 
12 /// The [`[registry]`](https://doc.rust-lang.org/cargo/reference/config.html#registry)
13 /// table controls the default registry used when one is not specified.
14 #[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
15 pub(crate) struct Registry {
16     /// name of the default registry
17     pub(crate) default: String,
18 
19     /// authentication token for crates.io
20     pub(crate) token: Option<String>,
21 }
22 
23 /// The [`[source]`](https://doc.rust-lang.org/cargo/reference/config.html#source)
24 /// table defines the registry sources available.
25 #[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
26 pub(crate) struct Source {
27     /// replace this source with the given named source
28     #[serde(rename = "replace-with")]
29     pub(crate) replace_with: Option<String>,
30 
31     /// URL to a registry source
32     #[serde(default = "default_registry_url")]
33     pub(crate) registry: String,
34 }
35 
36 /// This is the default registry url per what's defined by Cargo.
default_registry_url() -> String37 fn default_registry_url() -> String {
38     utils::CRATES_IO_INDEX_URL.to_owned()
39 }
40 
41 #[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
42 /// registries other than crates.io
43 pub(crate) struct AdditionalRegistry {
44     /// URL of the registry index
45     pub(crate) index: String,
46 
47     /// authentication token for the registry
48     pub(crate) token: Option<String>,
49 }
50 
51 /// A subset of a Cargo configuration file. The schema here is only what
52 /// is required for parsing registry information.
53 /// See [cargo docs](https://doc.rust-lang.org/cargo/reference/config.html#configuration-format)
54 /// for more details.
55 #[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
56 pub(crate) struct CargoConfig {
57     /// registries other than crates.io
58     #[serde(default = "default_registries")]
59     pub(crate) registries: BTreeMap<String, AdditionalRegistry>,
60 
61     #[serde(default = "default_registry")]
62     pub(crate) registry: Registry,
63 
64     /// source definition and replacement
65     #[serde(default = "BTreeMap::new")]
66     pub(crate) source: BTreeMap<String, Source>,
67 }
68 
69 /// Each Cargo config is expected to have a default `crates-io` registry.
default_registries() -> BTreeMap<String, AdditionalRegistry>70 fn default_registries() -> BTreeMap<String, AdditionalRegistry> {
71     let mut registries = BTreeMap::new();
72     registries.insert(
73         "crates-io".to_owned(),
74         AdditionalRegistry {
75             index: default_registry_url(),
76             token: None,
77         },
78     );
79     registries
80 }
81 
82 /// Each Cargo config has a default registry for `crates.io`.
default_registry() -> Registry83 fn default_registry() -> Registry {
84     Registry {
85         default: "crates-io".to_owned(),
86         token: None,
87     }
88 }
89 
90 impl Default for CargoConfig {
default() -> Self91     fn default() -> Self {
92         let registries = default_registries();
93         let registry = default_registry();
94         let source = Default::default();
95 
96         Self {
97             registries,
98             registry,
99             source,
100         }
101     }
102 }
103 
104 impl FromStr for CargoConfig {
105     type Err = anyhow::Error;
106 
from_str(s: &str) -> Result<Self, Self::Err>107     fn from_str(s: &str) -> Result<Self, Self::Err> {
108         let incoming: CargoConfig = toml::from_str(s)?;
109         let mut config = Self::default();
110         config.registries.extend(incoming.registries);
111         config.source.extend(incoming.source);
112         config.registry = incoming.registry;
113         Ok(config)
114     }
115 }
116 
117 impl CargoConfig {
118     /// Load a Cargo config from a path to a file on disk.
try_from_path(path: &Path) -> Result<Self>119     pub(crate) fn try_from_path(path: &Path) -> Result<Self> {
120         let content = fs::read_to_string(path)?;
121         Self::from_str(&content)
122     }
123 
124     /// Look up a registry [Source] by its url.
get_source_from_url(&self, url: &str) -> Option<&Source>125     pub(crate) fn get_source_from_url(&self, url: &str) -> Option<&Source> {
126         if let Some(found) = self.source.values().find(|v| v.registry == url) {
127             Some(found)
128         } else if url == utils::CRATES_IO_INDEX_URL {
129             self.source.get("crates-io")
130         } else {
131             None
132         }
133     }
134 
get_registry_index_url_by_name(&self, name: &str) -> Option<&str>135     pub(crate) fn get_registry_index_url_by_name(&self, name: &str) -> Option<&str> {
136         if let Some(registry) = self.registries.get(name) {
137             Some(&registry.index)
138         } else if let Some(source) = self.source.get(name) {
139             Some(&source.registry)
140         } else {
141             None
142         }
143     }
144 
resolve_replacement_url<'a>(&'a self, url: &'a str) -> Result<&'a str>145     pub(crate) fn resolve_replacement_url<'a>(&'a self, url: &'a str) -> Result<&'a str> {
146         if let Some(source) = self.get_source_from_url(url) {
147             if let Some(replace_with) = &source.replace_with {
148                 if let Some(replacement) = self.get_registry_index_url_by_name(replace_with) {
149                     Ok(replacement)
150                 } else {
151                     bail!("Tried to replace registry {} with registry named {} but didn't have metadata about the replacement", url, replace_with);
152                 }
153             } else {
154                 Ok(url)
155             }
156         } else {
157             Ok(url)
158         }
159     }
160 }
161 
162 #[cfg(test)]
163 mod test {
164     use super::*;
165 
166     use std::fs;
167 
168     #[test]
registry_settings()169     fn registry_settings() {
170         let temp_dir = tempfile::tempdir().unwrap();
171         let config = temp_dir.as_ref().join("config.toml");
172 
173         fs::write(&config, textwrap::dedent(
174             r#"
175                 # Makes artifactory the default registry and saves passing --registry parameter
176                 [registry]
177                 default = "art-crates-remote"
178 
179                 [registries]
180                 # Remote repository proxy in Artifactory (read-only)
181                 art-crates-remote = { index = "https://artprod.mycompany/artifactory/git/cargo-remote.git" }
182 
183                 # Optional, use with --registry to publish to crates.io
184                 crates-io = { index = "https://github.com/rust-lang/crates.io-index" }
185 
186                 [net]
187                 git-fetch-with-cli = true
188             "#,
189         )).unwrap();
190 
191         let config = CargoConfig::try_from_path(&config).unwrap();
192         assert_eq!(
193             config,
194             CargoConfig {
195                 registries: BTreeMap::from([
196                     (
197                         "art-crates-remote".to_owned(),
198                         AdditionalRegistry {
199                             index: "https://artprod.mycompany/artifactory/git/cargo-remote.git"
200                                 .to_owned(),
201                             token: None,
202                         },
203                     ),
204                     (
205                         "crates-io".to_owned(),
206                         AdditionalRegistry {
207                             index: "https://github.com/rust-lang/crates.io-index".to_owned(),
208                             token: None,
209                         },
210                     ),
211                 ]),
212                 registry: Registry {
213                     default: "art-crates-remote".to_owned(),
214                     token: None,
215                 },
216                 source: BTreeMap::new(),
217             },
218         )
219     }
220 
221     #[test]
registry_settings_get_index_url_by_name_from_source()222     fn registry_settings_get_index_url_by_name_from_source() {
223         let temp_dir = tempfile::tempdir().unwrap();
224         let config = temp_dir.as_ref().join("config.toml");
225 
226         fs::write(&config, textwrap::dedent(
227             r#"
228                 [registries]
229                 art-crates-remote = { index = "https://artprod.mycompany/artifactory/git/cargo-remote.git" }
230 
231                 [source.crates-io]
232                 replace-with = "some-mirror"
233 
234                 [source.some-mirror]
235                 registry = "https://artmirror.mycompany/artifactory/cargo-mirror.git"
236             "#,
237         )).unwrap();
238 
239         let config = CargoConfig::try_from_path(&config).unwrap();
240         assert_eq!(
241             config.get_registry_index_url_by_name("some-mirror"),
242             Some("https://artmirror.mycompany/artifactory/cargo-mirror.git"),
243         );
244     }
245 
246     #[test]
registry_settings_get_index_url_by_name_from_registry()247     fn registry_settings_get_index_url_by_name_from_registry() {
248         let temp_dir = tempfile::tempdir().unwrap();
249         let config = temp_dir.as_ref().join("config.toml");
250 
251         fs::write(&config, textwrap::dedent(
252             r#"
253                 [registries]
254                 art-crates-remote = { index = "https://artprod.mycompany/artifactory/git/cargo-remote.git" }
255 
256                 [source.crates-io]
257                 replace-with = "art-crates-remote"
258             "#,
259         )).unwrap();
260 
261         let config = CargoConfig::try_from_path(&config).unwrap();
262         assert_eq!(
263             config.get_registry_index_url_by_name("art-crates-remote"),
264             Some("https://artprod.mycompany/artifactory/git/cargo-remote.git"),
265         );
266     }
267 
268     #[test]
registry_settings_get_source_from_url()269     fn registry_settings_get_source_from_url() {
270         let temp_dir = tempfile::tempdir().unwrap();
271         let config = temp_dir.as_ref().join("config.toml");
272 
273         fs::write(
274             &config,
275             textwrap::dedent(
276                 r#"
277                 [source.some-mirror]
278                 registry = "https://artmirror.mycompany/artifactory/cargo-mirror.git"
279             "#,
280             ),
281         )
282         .unwrap();
283 
284         let config = CargoConfig::try_from_path(&config).unwrap();
285         assert_eq!(
286             config
287                 .get_source_from_url("https://artmirror.mycompany/artifactory/cargo-mirror.git")
288                 .map(|s| s.registry.as_str()),
289             Some("https://artmirror.mycompany/artifactory/cargo-mirror.git"),
290         );
291     }
292 
293     #[test]
resolve_replacement_url_no_replacement()294     fn resolve_replacement_url_no_replacement() {
295         let temp_dir = tempfile::tempdir().unwrap();
296         let config = temp_dir.as_ref().join("config.toml");
297 
298         fs::write(&config, "").unwrap();
299 
300         let config = CargoConfig::try_from_path(&config).unwrap();
301 
302         assert_eq!(
303             config
304                 .resolve_replacement_url(utils::CRATES_IO_INDEX_URL)
305                 .unwrap(),
306             utils::CRATES_IO_INDEX_URL
307         );
308         assert_eq!(
309             config
310                 .resolve_replacement_url("https://artmirror.mycompany/artifactory/cargo-mirror.git")
311                 .unwrap(),
312             "https://artmirror.mycompany/artifactory/cargo-mirror.git"
313         );
314     }
315 
316     #[test]
resolve_replacement_url_registry()317     fn resolve_replacement_url_registry() {
318         let temp_dir = tempfile::tempdir().unwrap();
319         let config = temp_dir.as_ref().join("config.toml");
320 
321         fs::write(&config, textwrap::dedent(
322             r#"
323                 [registries]
324                 art-crates-remote = { index = "https://artprod.mycompany/artifactory/git/cargo-remote.git" }
325 
326                 [source.crates-io]
327                 replace-with = "some-mirror"
328 
329                 [source.some-mirror]
330                 registry = "https://artmirror.mycompany/artifactory/cargo-mirror.git"
331             "#,
332         )).unwrap();
333 
334         let config = CargoConfig::try_from_path(&config).unwrap();
335         assert_eq!(
336             config
337                 .resolve_replacement_url(utils::CRATES_IO_INDEX_URL)
338                 .unwrap(),
339             "https://artmirror.mycompany/artifactory/cargo-mirror.git"
340         );
341         assert_eq!(
342             config
343                 .resolve_replacement_url("https://artmirror.mycompany/artifactory/cargo-mirror.git")
344                 .unwrap(),
345             "https://artmirror.mycompany/artifactory/cargo-mirror.git"
346         );
347         assert_eq!(
348             config
349                 .resolve_replacement_url(
350                     "https://artprod.mycompany/artifactory/git/cargo-remote.git"
351                 )
352                 .unwrap(),
353             "https://artprod.mycompany/artifactory/git/cargo-remote.git"
354         );
355     }
356 
357     #[test]
resolve_replacement_url_source()358     fn resolve_replacement_url_source() {
359         let temp_dir = tempfile::tempdir().unwrap();
360         let config = temp_dir.as_ref().join("config.toml");
361 
362         fs::write(&config, textwrap::dedent(
363             r#"
364                 [registries]
365                 art-crates-remote = { index = "https://artprod.mycompany/artifactory/git/cargo-remote.git" }
366 
367                 [source.crates-io]
368                 replace-with = "art-crates-remote"
369 
370                 [source.some-mirror]
371                 registry = "https://artmirror.mycompany/artifactory/cargo-mirror.git"
372             "#,
373         )).unwrap();
374 
375         let config = CargoConfig::try_from_path(&config).unwrap();
376         assert_eq!(
377             config
378                 .resolve_replacement_url(utils::CRATES_IO_INDEX_URL)
379                 .unwrap(),
380             "https://artprod.mycompany/artifactory/git/cargo-remote.git"
381         );
382         assert_eq!(
383             config
384                 .resolve_replacement_url("https://artmirror.mycompany/artifactory/cargo-mirror.git")
385                 .unwrap(),
386             "https://artmirror.mycompany/artifactory/cargo-mirror.git"
387         );
388         assert_eq!(
389             config
390                 .resolve_replacement_url(
391                     "https://artprod.mycompany/artifactory/git/cargo-remote.git"
392                 )
393                 .unwrap(),
394             "https://artprod.mycompany/artifactory/git/cargo-remote.git"
395         );
396     }
397 }
398