diff --git a/crates/cargo-util-schemas/registry_config.schema.json b/crates/cargo-util-schemas/registry_config.schema.json new file mode 100644 index 00000000000..647694b0dbb --- /dev/null +++ b/crates/cargo-util-schemas/registry_config.schema.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "RegistryConfig", + "description": "The [`config.json`] file stored in the index.\n\nThe config file may look like:\n\n```json\n{\n \"dl\": \"https://example.com/api/{crate}/{version}/download\",\n \"api\": \"https://example.com/api\",\n \"auth-required\": false\n}\n```\n\n[`config.json`]: https://doc.rust-lang.org/nightly/cargo/reference/registry-index.html#index-configuration", + "type": "object", + "properties": { + "dl": { + "description": "Download endpoint for all crates.\n\nThe string is a template which will generate the download URL for the\ntarball of a specific version of a crate. The substrings `{crate}` and\n`{version}` will be replaced with the crate's name and version\nrespectively. The substring `{prefix}` will be replaced with the\ncrate's prefix directory name, and the substring `{lowerprefix}` will\nbe replaced with the crate's prefix directory name converted to\nlowercase. The substring `{sha256-checksum}` will be replaced with the\ncrate's sha256 checksum.\n\nFor backwards compatibility, if the string does not contain any\nmarkers (`{crate}`, `{version}`, `{prefix}`, or `{lowerprefix}`), it\nwill be extended with `/{crate}/{version}/download` to\nsupport registries like crates.io which were created before the\ntemplating setup was created.\n\nFor more on the template of the download URL, see [Index Configuration](\nhttps://doc.rust-lang.org/nightly/cargo/reference/registry-index.html#index-configuration).", + "type": "string" + }, + "api": { + "description": "API endpoint for the registry. This is what's actually hit to perform\noperations like yanks, owner modifications, publish new crates, etc.\nIf this is None, the registry does not support API commands.", + "type": [ + "string", + "null" + ] + }, + "auth-required": { + "description": "Whether all operations require authentication. See [RFC 3139].\n\n[RFC 3139]: https://rust-lang.github.io/rfcs/3139-cargo-alternative-registry-auth.html", + "type": "boolean", + "default": false + } + }, + "required": [ + "dl" + ] +} \ No newline at end of file diff --git a/crates/cargo-util-schemas/src/index.rs b/crates/cargo-util-schemas/src/index.rs index 5f58dfadb7c..2ba070c70d2 100644 --- a/crates/cargo-util-schemas/src/index.rs +++ b/crates/cargo-util-schemas/src/index.rs @@ -244,6 +244,69 @@ fn dump_index_schema() { snapbox::assert_data_eq!(dump, snapbox::file!("../index.schema.json").raw()); } +/// The [`config.json`] file stored in the index. +/// +/// The config file may look like: +/// +/// ```json +/// { +/// "dl": "https://example.com/api/{crate}/{version}/download", +/// "api": "https://example.com/api", +/// "auth-required": false +/// } +/// ``` +/// +/// [`config.json`]: https://doc.rust-lang.org/nightly/cargo/reference/registry-index.html#index-configuration +#[derive(Deserialize, Serialize, Debug, Clone)] +#[serde(rename_all = "kebab-case")] +#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] +pub struct RegistryConfig { + /// Download endpoint for all crates. + /// + /// The string is a template which will generate the download URL for the + /// tarball of a specific version of a crate. The substrings `{crate}` and + /// `{version}` will be replaced with the crate's name and version + /// respectively. The substring `{prefix}` will be replaced with the + /// crate's prefix directory name, and the substring `{lowerprefix}` will + /// be replaced with the crate's prefix directory name converted to + /// lowercase. The substring `{sha256-checksum}` will be replaced with the + /// crate's sha256 checksum. + /// + /// For backwards compatibility, if the string does not contain any + /// markers (`{crate}`, `{version}`, `{prefix}`, or `{lowerprefix}`), it + /// will be extended with `/{crate}/{version}/download` to + /// support registries like crates.io which were created before the + /// templating setup was created. + /// + /// For more on the template of the download URL, see [Index Configuration]( + /// https://doc.rust-lang.org/nightly/cargo/reference/registry-index.html#index-configuration). + pub dl: String, + + /// API endpoint for the registry. This is what's actually hit to perform + /// operations like yanks, owner modifications, publish new crates, etc. + /// If this is None, the registry does not support API commands. + pub api: Option, + + /// Whether all operations require authentication. See [RFC 3139]. + /// + /// [RFC 3139]: https://rust-lang.github.io/rfcs/3139-cargo-alternative-registry-auth.html + #[serde(default)] + pub auth_required: bool, +} + +impl RegistryConfig { + /// File name of [`RegistryConfig`]. + pub const NAME: &'static str = "config.json"; +} + +#[cfg(feature = "unstable-schema")] +#[test] +fn dump_registry_schema() { + let schema = schemars::schema_for!(crate::index::RegistryConfig); + let dump = serde_json::to_string_pretty(&schema).unwrap(); + snapbox::assert_data_eq!(dump, snapbox::file!("../registry_config.schema.json").raw()); +} + #[test] fn pubtime_format() { use snapbox::str; diff --git a/crates/cargo-util/src/registry.rs b/crates/cargo-util/src/registry.rs index febb29bdea4..0627af890a9 100644 --- a/crates/cargo-util/src/registry.rs +++ b/crates/cargo-util/src/registry.rs @@ -25,6 +25,35 @@ pub fn make_dep_path(dep_name: &str, prefix_only: bool) -> String { } } +pub fn crate_url(dl_template: &str, krate: &str, version: &str, sha256_checksum: &str) -> String { + let url = if !dl_template.contains(CRATE_TEMPLATE) + && !dl_template.contains(VERSION_TEMPLATE) + && !dl_template.contains(PREFIX_TEMPLATE) + && !dl_template.contains(LOWER_PREFIX_TEMPLATE) + && !dl_template.contains(CHECKSUM_TEMPLATE) + { + // Original format before customizing the download URL was supported. + format!("{dl_template}/{krate}/{version}/download") + } else { + let prefix = make_dep_path(krate, true); + let lowerprefix = prefix.to_lowercase(); + dl_template + .replace(CRATE_TEMPLATE, krate) + .replace(VERSION_TEMPLATE, version) + .replace(PREFIX_TEMPLATE, &prefix) + .replace(LOWER_PREFIX_TEMPLATE, &lowerprefix) + .replace(CHECKSUM_TEMPLATE, sha256_checksum) + }; + + url +} + +const CRATE_TEMPLATE: &str = "{crate}"; +const VERSION_TEMPLATE: &str = "{version}"; +const PREFIX_TEMPLATE: &str = "{prefix}"; +const LOWER_PREFIX_TEMPLATE: &str = "{lowerprefix}"; +const CHECKSUM_TEMPLATE: &str = "{sha256-checksum}"; + #[cfg(test)] mod tests { use super::make_dep_path; diff --git a/src/cargo/sources/registry/download.rs b/src/cargo/sources/registry/download.rs index 5be518d72af..551b3dbbb7b 100644 --- a/src/cargo/sources/registry/download.rs +++ b/src/cargo/sources/registry/download.rs @@ -7,7 +7,7 @@ use crate::util::interning::InternedString; use anyhow::Context as _; use cargo_credential::Operation; use cargo_util::Sha256; -use cargo_util::registry::make_dep_path; +use cargo_util::registry::crate_url; use crate::core::PackageId; use crate::core::global_cache_tracker; @@ -17,18 +17,11 @@ use crate::util::auth; use crate::util::cache_lock::CacheLockMode; use crate::util::errors::CargoResult; use crate::util::{Filesystem, GlobalContext}; -use std::fmt::Write as FmtWrite; use std::fs::{self, File, OpenOptions}; use std::io::SeekFrom; use std::io::prelude::*; use std::str; -const CRATE_TEMPLATE: &str = "{crate}"; -const VERSION_TEMPLATE: &str = "{version}"; -const PREFIX_TEMPLATE: &str = "{prefix}"; -const LOWER_PREFIX_TEMPLATE: &str = "{lowerprefix}"; -const CHECKSUM_TEMPLATE: &str = "{sha256-checksum}"; - /// Checks if `pkg` is downloaded and ready under the directory at `cache_path`. /// If not, returns a URL to download it from. /// @@ -64,24 +57,12 @@ pub(super) fn download( } } - let mut url = registry_config.dl; - if !url.contains(CRATE_TEMPLATE) - && !url.contains(VERSION_TEMPLATE) - && !url.contains(PREFIX_TEMPLATE) - && !url.contains(LOWER_PREFIX_TEMPLATE) - && !url.contains(CHECKSUM_TEMPLATE) - { - // Original format before customizing the download URL was supported. - write!(url, "/{}/{}/download", pkg.name(), pkg.version()).unwrap(); - } else { - let prefix = make_dep_path(&pkg.name(), true); - url = url - .replace(CRATE_TEMPLATE, &*pkg.name()) - .replace(VERSION_TEMPLATE, &pkg.version().to_string()) - .replace(PREFIX_TEMPLATE, &prefix) - .replace(LOWER_PREFIX_TEMPLATE, &prefix.to_lowercase()) - .replace(CHECKSUM_TEMPLATE, checksum); - } + let url = crate_url( + ®istry_config.dl, + &*pkg.name(), + &pkg.version().to_string(), + checksum, + ); let authorization = if registry_config.auth_required { Some(auth::auth_token( diff --git a/src/cargo/sources/registry/mod.rs b/src/cargo/sources/registry/mod.rs index 441278fc058..24170b63cbb 100644 --- a/src/cargo/sources/registry/mod.rs +++ b/src/cargo/sources/registry/mod.rs @@ -213,6 +213,8 @@ use crate::util::interning::InternedString; use crate::util::{CargoResult, Filesystem, GlobalContext, LimitErrorReader, restricted_names}; use crate::util::{VersionExt, hex}; +pub use cargo_util_schemas::index::RegistryConfig; + /// The `.cargo-ok` file is used to track if the source is already unpacked. /// See [`RegistrySource::unpack_package`] for more. /// @@ -270,55 +272,6 @@ pub struct RegistrySource<'gctx> { selected_precise_yanked: RefCell>, } -/// The [`config.json`] file stored in the index. -/// -/// The config file may look like: -/// -/// ```json -/// { -/// "dl": "https://example.com/api/{crate}/{version}/download", -/// "api": "https://example.com/api", -/// "auth-required": false # unstable feature (RFC 3139) -/// } -/// ``` -/// -/// [`config.json`]: https://doc.rust-lang.org/nightly/cargo/reference/registry-index.html#index-configuration -#[derive(Deserialize, Debug, Clone)] -#[serde(rename_all = "kebab-case")] -pub struct RegistryConfig { - /// Download endpoint for all crates. - /// - /// The string is a template which will generate the download URL for the - /// tarball of a specific version of a crate. The substrings `{crate}` and - /// `{version}` will be replaced with the crate's name and version - /// respectively. The substring `{prefix}` will be replaced with the - /// crate's prefix directory name, and the substring `{lowerprefix}` will - /// be replaced with the crate's prefix directory name converted to - /// lowercase. The substring `{sha256-checksum}` will be replaced with the - /// crate's sha256 checksum. - /// - /// For backwards compatibility, if the string does not contain any - /// markers (`{crate}`, `{version}`, `{prefix}`, or `{lowerprefix}`), it - /// will be extended with `/{crate}/{version}/download` to - /// support registries like crates.io which were created before the - /// templating setup was created. - /// - /// For more on the template of the download URL, see [Index Configuration]( - /// https://doc.rust-lang.org/nightly/cargo/reference/registry-index.html#index-configuration). - pub dl: String, - - /// API endpoint for the registry. This is what's actually hit to perform - /// operations like yanks, owner modifications, publish new crates, etc. - /// If this is None, the registry does not support API commands. - pub api: Option, - - /// Whether all operations require authentication. See [RFC 3139]. - /// - /// [RFC 3139]: https://rust-lang.github.io/rfcs/3139-cargo-alternative-registry-auth.html - #[serde(default)] - pub auth_required: bool, -} - /// Result from loading data from a registry. #[derive(Debug, Clone)] pub enum LoadResponse { @@ -965,11 +918,6 @@ impl<'gctx> Source for RegistrySource<'gctx> { } } -impl RegistryConfig { - /// File name of [`RegistryConfig`]. - const NAME: &'static str = "config.json"; -} - /// Get the maximum unpack size that Cargo permits /// based on a given `size` of your compressed file. ///