use std::path::{Path, PathBuf}; use std::collections::BTreeMap; use std::borrow::Cow; use serde::Deserialize; use clap::Parser; use tracing::{debug, error, info, warn}; use tracing_subscriber::filter::EnvFilter; use url::Url; use anyhow::{anyhow, bail, Error}; #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct DestinationRegistryConfig { pub api_url: Url, pub token: String, } #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct SourceRegistryConfig { pub index_dir: PathBuf, pub crate_files_dir: PathBuf, } #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct Config { #[serde(alias = "source")] pub src: SourceRegistryConfig, #[serde(alias = "destination")] pub dst: DestinationRegistryConfig, } /// fields we need from Cargo.toml [package] section to combine with IndexMeta /// to form a PublishMeta. #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] pub struct PackageStub { pub name: String, pub version: Version, #[serde(default)] pub authors: Vec, pub description: Option, pub license: Option, pub license_file: Option, #[serde(default)] pub categories: Vec, #[serde(default)] pub keywords: Vec, pub readme: Option, pub repository: Option, pub homepage: Option, pub documentation: Option, } /// for parsing Cargo.toml to extract missing PublishMeta fields that do not appear /// in IndexMeta #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] pub struct ManifestStub { pub package: PackageStub, } /// full definition of cargo publish json #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] pub struct PublishMeta { #[serde(borrow)] pub name: String, #[serde(alias = "version")] pub vers: semver::Version, #[serde(alias = "dependencies")] #[serde(default)] pub deps: Vec, #[serde(default, borrow)] pub features: BTreeMap>, #[serde(default, borrow)] pub authors: Vec, #[serde(borrow)] pub description: Option, #[serde(borrow)] pub documentation: Option, #[serde(borrow)] pub homepage: Option, #[serde(borrow)] pub readme: Option, #[serde(borrow)] pub readme_file: Option, #[serde(default, borrow)] pub keywords: Vec, #[serde(default, borrow)] pub categories: Vec, #[serde(borrow)] pub license: Option, #[serde(borrow)] pub license_file: Option, #[serde(borrow)] pub repository: Option, #[serde(skip_serializing_if = "Option::is_none", borrow)] pub links: Option, #[serde(skip_serializing_if = "Option::is_none", borrow)] pub badges: Option>, /// from ancient cargo versions #[serde(skip_serializing_if = "Option::is_none", borrow)] pub features2: Option>>, /// from ancient cargo versions #[serde(skip_serializing_if = "Option::is_none")] pub v: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] pub struct PublishDependency { pub optional: bool, pub default_features: bool, #[serde(borrow)] pub name: String, #[serde(borrow)] pub features: Vec, // cargo and crates-io have this as string #[serde(alias = "req")] pub version_req: semver::VersionReq, #[serde(borrow)] pub target: Option, // crates-io has this as option pub kind: PublishDependencyKind, #[serde(skip_serializing_if = "Option::is_none", borrow)] pub registry: Option, #[serde(skip_serializing_if = "Option::is_none", borrow)] pub explicit_name_in_toml: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] pub struct IndexMeta { // same everything as publish metadata #[serde(borrow)] pub name: String, #[serde(alias = "version")] pub vers: semver::Version, #[serde(alias = "dependencies", borrow)] pub features: BTreeMap>, #[serde(skip_serializing_if = "Option::is_none", borrow)] pub links: Option, #[serde(skip_serializing_if = "Option::is_none", borrow)] pub badges: Option>, // modified format/field names pub deps: Vec, // fields that don't appear in publish metadata pub cksum: String, pub yanked: bool, // ancient fields, these were actually written // on sanskrit on stone tablets #[serde(skip_serializing_if = "Option::is_none", borrow)] pub features2: Option>>, #[serde(skip_serializing_if = "Option::is_none")] pub v: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] pub struct IndexDependency { /// corresponds to `explicit_name_in_toml` field in `publish::Dependency` /// when a dep is renamed in Cargo.toml, otherwise same as `package`. #[serde(borrow)] pub name: String, /// corresponds to `name` in `publish::Dependency` #[serde(skip_serializing_if = "Option::is_none", borrow)] pub package: Option, /// in publish meta, this field is called `version_req`, and the index /// format requires it to be renamed to `req` #[serde(alias = "version_req")] pub req: semver::VersionReq, #[serde(borrow)] pub features: Vec, pub optional: bool, pub default_features: bool, #[serde(borrow)] pub target: Option, pub kind: DependencyKind, #[serde(skip_serializing_if = "Option::is_none", borrow)] pub registry: Option, } /// Section in which this dependency was defined #[derive(Copy, Clone, Serialize, Deserialize, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)] #[serde(rename_all = "lowercase")] pub enum DependencyKind { /// Used at run time Normal, /// Used at build time, not available at run time Build, /// Not fetched and not used, except for when used direclty in a workspace Dev, } fn extract_manifest_from_tar(rdr: R) -> Result, Error> { let mut archive = tar::Archive::new(rdr); for entry in archive.entries()? { let mut entry = entry?; let path = entry.path()?; if path.ends_with("Cargo.toml.orig") { let mut manifest_toml = String::new(); entry.read_to_string(&mut manifest_toml)?; return Ok(Some(manifest_toml)) } } Ok(None) } fn extract_readme_from_tar(rdr: R, readme_path: &Path) -> Result, Error> { let mut archive = tar::Archive::new(rdr); for entry in archive.entries()? { let mut entry = entry?; let path = entry.path()?; if path == readme_path || path.ends_with(readme_path) { let mut out = String::new(); entry.read_to_string(&mut out)?; return Ok(Some(out)) } } Ok(None) }