| //! Tools for parsing [Cargo configuration](https://doc.rust-lang.org/cargo/reference/config.html) files |
| |
| use std::collections::BTreeMap; |
| use std::fs; |
| use std::path::Path; |
| use std::str::FromStr; |
| |
| use crate::utils; |
| use anyhow::{bail, Result}; |
| use serde::{Deserialize, Serialize}; |
| |
| /// The [`[registry]`](https://doc.rust-lang.org/cargo/reference/config.html#registry) |
| /// table controls the default registry used when one is not specified. |
| #[derive(Debug, Deserialize, Serialize, PartialEq, Eq)] |
| pub(crate) struct Registry { |
| /// name of the default registry |
| pub(crate) default: String, |
| |
| /// authentication token for crates.io |
| pub(crate) token: Option<String>, |
| } |
| |
| /// The [`[source]`](https://doc.rust-lang.org/cargo/reference/config.html#source) |
| /// table defines the registry sources available. |
| #[derive(Debug, Deserialize, Serialize, PartialEq, Eq)] |
| pub(crate) struct Source { |
| /// replace this source with the given named source |
| #[serde(rename = "replace-with")] |
| pub(crate) replace_with: Option<String>, |
| |
| /// URL to a registry source |
| #[serde(default = "default_registry_url")] |
| pub(crate) registry: String, |
| } |
| |
| /// This is the default registry url per what's defined by Cargo. |
| fn default_registry_url() -> String { |
| utils::CRATES_IO_INDEX_URL.to_owned() |
| } |
| |
| #[derive(Debug, Deserialize, Serialize, PartialEq, Eq)] |
| /// registries other than crates.io |
| pub(crate) struct AdditionalRegistry { |
| /// URL of the registry index |
| pub(crate) index: String, |
| |
| /// authentication token for the registry |
| pub(crate) token: Option<String>, |
| } |
| |
| /// A subset of a Cargo configuration file. The schema here is only what |
| /// is required for parsing registry information. |
| /// See [cargo docs](https://doc.rust-lang.org/cargo/reference/config.html#configuration-format) |
| /// for more details. |
| #[derive(Debug, Deserialize, Serialize, PartialEq, Eq)] |
| pub(crate) struct CargoConfig { |
| /// registries other than crates.io |
| #[serde(default = "default_registries")] |
| pub(crate) registries: BTreeMap<String, AdditionalRegistry>, |
| |
| #[serde(default = "default_registry")] |
| pub(crate) registry: Registry, |
| |
| /// source definition and replacement |
| #[serde(default = "BTreeMap::new")] |
| pub(crate) source: BTreeMap<String, Source>, |
| } |
| |
| /// Each Cargo config is expected to have a default `crates-io` registry. |
| fn default_registries() -> BTreeMap<String, AdditionalRegistry> { |
| let mut registries = BTreeMap::new(); |
| registries.insert( |
| "crates-io".to_owned(), |
| AdditionalRegistry { |
| index: default_registry_url(), |
| token: None, |
| }, |
| ); |
| registries |
| } |
| |
| /// Each Cargo config has a default registry for `crates.io`. |
| fn default_registry() -> Registry { |
| Registry { |
| default: "crates-io".to_owned(), |
| token: None, |
| } |
| } |
| |
| impl Default for CargoConfig { |
| fn default() -> Self { |
| let registries = default_registries(); |
| let registry = default_registry(); |
| let source = Default::default(); |
| |
| Self { |
| registries, |
| registry, |
| source, |
| } |
| } |
| } |
| |
| impl FromStr for CargoConfig { |
| type Err = anyhow::Error; |
| |
| fn from_str(s: &str) -> Result<Self, Self::Err> { |
| let incoming: CargoConfig = toml::from_str(s)?; |
| let mut config = Self::default(); |
| config.registries.extend(incoming.registries); |
| config.source.extend(incoming.source); |
| config.registry = incoming.registry; |
| Ok(config) |
| } |
| } |
| |
| impl CargoConfig { |
| /// Load a Cargo config from a path to a file on disk. |
| pub(crate) fn try_from_path(path: &Path) -> Result<Self> { |
| let content = fs::read_to_string(path)?; |
| Self::from_str(&content) |
| } |
| |
| /// Look up a registry [Source] by its url. |
| pub(crate) fn get_source_from_url(&self, url: &str) -> Option<&Source> { |
| if let Some(found) = self.source.values().find(|v| v.registry == url) { |
| Some(found) |
| } else if url == utils::CRATES_IO_INDEX_URL { |
| self.source.get("crates-io") |
| } else { |
| None |
| } |
| } |
| |
| pub(crate) fn get_registry_index_url_by_name(&self, name: &str) -> Option<&str> { |
| if let Some(registry) = self.registries.get(name) { |
| Some(®istry.index) |
| } else if let Some(source) = self.source.get(name) { |
| Some(&source.registry) |
| } else { |
| None |
| } |
| } |
| |
| pub(crate) fn resolve_replacement_url<'a>(&'a self, url: &'a str) -> Result<&'a str> { |
| if let Some(source) = self.get_source_from_url(url) { |
| if let Some(replace_with) = &source.replace_with { |
| if let Some(replacement) = self.get_registry_index_url_by_name(replace_with) { |
| Ok(replacement) |
| } else { |
| bail!("Tried to replace registry {} with registry named {} but didn't have metadata about the replacement", url, replace_with); |
| } |
| } else { |
| Ok(url) |
| } |
| } else { |
| Ok(url) |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::*; |
| |
| #[test] |
| fn registry_settings() { |
| let temp_dir = tempfile::tempdir().unwrap(); |
| let config = temp_dir.as_ref().join("config.toml"); |
| |
| fs::write(&config, textwrap::dedent( |
| r#" |
| # Makes artifactory the default registry and saves passing --registry parameter |
| [registry] |
| default = "art-crates-remote" |
| |
| [registries] |
| # Remote repository proxy in Artifactory (read-only) |
| art-crates-remote = { index = "https://artprod.mycompany/artifactory/git/cargo-remote.git" } |
| |
| # Optional, use with --registry to publish to crates.io |
| crates-io = { index = "https://github.com/rust-lang/crates.io-index" } |
| |
| [net] |
| git-fetch-with-cli = true |
| "#, |
| )).unwrap(); |
| |
| let config = CargoConfig::try_from_path(&config).unwrap(); |
| assert_eq!( |
| config, |
| CargoConfig { |
| registries: BTreeMap::from([ |
| ( |
| "art-crates-remote".to_owned(), |
| AdditionalRegistry { |
| index: "https://artprod.mycompany/artifactory/git/cargo-remote.git" |
| .to_owned(), |
| token: None, |
| }, |
| ), |
| ( |
| "crates-io".to_owned(), |
| AdditionalRegistry { |
| index: "https://github.com/rust-lang/crates.io-index".to_owned(), |
| token: None, |
| }, |
| ), |
| ]), |
| registry: Registry { |
| default: "art-crates-remote".to_owned(), |
| token: None, |
| }, |
| source: BTreeMap::new(), |
| }, |
| ) |
| } |
| |
| #[test] |
| fn registry_settings_get_index_url_by_name_from_source() { |
| let temp_dir = tempfile::tempdir().unwrap(); |
| let config = temp_dir.as_ref().join("config.toml"); |
| |
| fs::write(&config, textwrap::dedent( |
| r#" |
| [registries] |
| art-crates-remote = { index = "https://artprod.mycompany/artifactory/git/cargo-remote.git" } |
| |
| [source.crates-io] |
| replace-with = "some-mirror" |
| |
| [source.some-mirror] |
| registry = "https://artmirror.mycompany/artifactory/cargo-mirror.git" |
| "#, |
| )).unwrap(); |
| |
| let config = CargoConfig::try_from_path(&config).unwrap(); |
| assert_eq!( |
| config.get_registry_index_url_by_name("some-mirror"), |
| Some("https://artmirror.mycompany/artifactory/cargo-mirror.git"), |
| ); |
| } |
| |
| #[test] |
| fn registry_settings_get_index_url_by_name_from_registry() { |
| let temp_dir = tempfile::tempdir().unwrap(); |
| let config = temp_dir.as_ref().join("config.toml"); |
| |
| fs::write(&config, textwrap::dedent( |
| r#" |
| [registries] |
| art-crates-remote = { index = "https://artprod.mycompany/artifactory/git/cargo-remote.git" } |
| |
| [source.crates-io] |
| replace-with = "art-crates-remote" |
| "#, |
| )).unwrap(); |
| |
| let config = CargoConfig::try_from_path(&config).unwrap(); |
| assert_eq!( |
| config.get_registry_index_url_by_name("art-crates-remote"), |
| Some("https://artprod.mycompany/artifactory/git/cargo-remote.git"), |
| ); |
| } |
| |
| #[test] |
| fn registry_settings_get_source_from_url() { |
| let temp_dir = tempfile::tempdir().unwrap(); |
| let config = temp_dir.as_ref().join("config.toml"); |
| |
| fs::write( |
| &config, |
| textwrap::dedent( |
| r#" |
| [source.some-mirror] |
| registry = "https://artmirror.mycompany/artifactory/cargo-mirror.git" |
| "#, |
| ), |
| ) |
| .unwrap(); |
| |
| let config = CargoConfig::try_from_path(&config).unwrap(); |
| assert_eq!( |
| config |
| .get_source_from_url("https://artmirror.mycompany/artifactory/cargo-mirror.git") |
| .map(|s| s.registry.as_str()), |
| Some("https://artmirror.mycompany/artifactory/cargo-mirror.git"), |
| ); |
| } |
| |
| #[test] |
| fn resolve_replacement_url_no_replacement() { |
| let temp_dir = tempfile::tempdir().unwrap(); |
| let config = temp_dir.as_ref().join("config.toml"); |
| |
| fs::write(&config, "").unwrap(); |
| |
| let config = CargoConfig::try_from_path(&config).unwrap(); |
| |
| assert_eq!( |
| config |
| .resolve_replacement_url(utils::CRATES_IO_INDEX_URL) |
| .unwrap(), |
| utils::CRATES_IO_INDEX_URL |
| ); |
| assert_eq!( |
| config |
| .resolve_replacement_url("https://artmirror.mycompany/artifactory/cargo-mirror.git") |
| .unwrap(), |
| "https://artmirror.mycompany/artifactory/cargo-mirror.git" |
| ); |
| } |
| |
| #[test] |
| fn resolve_replacement_url_registry() { |
| let temp_dir = tempfile::tempdir().unwrap(); |
| let config = temp_dir.as_ref().join("config.toml"); |
| |
| fs::write(&config, textwrap::dedent( |
| r#" |
| [registries] |
| art-crates-remote = { index = "https://artprod.mycompany/artifactory/git/cargo-remote.git" } |
| |
| [source.crates-io] |
| replace-with = "some-mirror" |
| |
| [source.some-mirror] |
| registry = "https://artmirror.mycompany/artifactory/cargo-mirror.git" |
| "#, |
| )).unwrap(); |
| |
| let config = CargoConfig::try_from_path(&config).unwrap(); |
| assert_eq!( |
| config |
| .resolve_replacement_url(utils::CRATES_IO_INDEX_URL) |
| .unwrap(), |
| "https://artmirror.mycompany/artifactory/cargo-mirror.git" |
| ); |
| assert_eq!( |
| config |
| .resolve_replacement_url("https://artmirror.mycompany/artifactory/cargo-mirror.git") |
| .unwrap(), |
| "https://artmirror.mycompany/artifactory/cargo-mirror.git" |
| ); |
| assert_eq!( |
| config |
| .resolve_replacement_url( |
| "https://artprod.mycompany/artifactory/git/cargo-remote.git" |
| ) |
| .unwrap(), |
| "https://artprod.mycompany/artifactory/git/cargo-remote.git" |
| ); |
| } |
| |
| #[test] |
| fn resolve_replacement_url_source() { |
| let temp_dir = tempfile::tempdir().unwrap(); |
| let config = temp_dir.as_ref().join("config.toml"); |
| |
| fs::write(&config, textwrap::dedent( |
| r#" |
| [registries] |
| art-crates-remote = { index = "https://artprod.mycompany/artifactory/git/cargo-remote.git" } |
| |
| [source.crates-io] |
| replace-with = "art-crates-remote" |
| |
| [source.some-mirror] |
| registry = "https://artmirror.mycompany/artifactory/cargo-mirror.git" |
| "#, |
| )).unwrap(); |
| |
| let config = CargoConfig::try_from_path(&config).unwrap(); |
| assert_eq!( |
| config |
| .resolve_replacement_url(utils::CRATES_IO_INDEX_URL) |
| .unwrap(), |
| "https://artprod.mycompany/artifactory/git/cargo-remote.git" |
| ); |
| assert_eq!( |
| config |
| .resolve_replacement_url("https://artmirror.mycompany/artifactory/cargo-mirror.git") |
| .unwrap(), |
| "https://artmirror.mycompany/artifactory/cargo-mirror.git" |
| ); |
| assert_eq!( |
| config |
| .resolve_replacement_url( |
| "https://artprod.mycompany/artifactory/git/cargo-remote.git" |
| ) |
| .unwrap(), |
| "https://artprod.mycompany/artifactory/git/cargo-remote.git" |
| ); |
| } |
| } |