From a0b8d1a99440e7b8a20a9c7704cec70cf64af715 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Fri, 5 Apr 2024 10:40:33 -0400 Subject: [PATCH] Clean up `Error` enum in `Metadata23` (#2835) ## Summary Rename, and remove a bunch of unused variants. --- crates/pypi-types/src/metadata.rs | 110 ++++++++++++---------------- crates/uv-client/src/error.rs | 6 +- crates/uv-distribution/src/error.rs | 6 +- 3 files changed, 56 insertions(+), 66 deletions(-) diff --git a/crates/pypi-types/src/metadata.rs b/crates/pypi-types/src/metadata.rs index f3b267a00..a5653ed91 100644 --- a/crates/pypi-types/src/metadata.rs +++ b/crates/pypi-types/src/metadata.rs @@ -1,9 +1,8 @@ //! Derived from `pypi_types_crate`. -use indexmap::IndexMap; -use std::io; use std::str::FromStr; +use indexmap::IndexMap; use mailparse::{MailHeaderMap, MailParseError}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -39,38 +38,17 @@ pub struct Metadata23 { /// /// The error type #[derive(Error, Debug)] -pub enum Error { - /// I/O error - #[error(transparent)] - Io(#[from] io::Error), - /// mail parse error +pub enum MetadataError { #[error(transparent)] MailParse(#[from] MailParseError), - /// TOML parse error #[error(transparent)] Toml(#[from] toml::de::Error), - /// Metadata field not found #[error("metadata field {0} not found")] FieldNotFound(&'static str), - /// Unknown distribution type - #[error("unknown distribution type")] - UnknownDistributionType, - /// Metadata file not found - #[error("metadata file not found")] - MetadataNotFound, - /// Invalid project URL (no comma) - #[error("Invalid Project-URL field (missing comma): '{0}'")] - InvalidProjectUrl(String), - /// Multiple metadata files found - #[error("found multiple metadata files: {0:?}")] - MultipleMetadataFiles(Vec), - /// Invalid Version #[error("invalid version: {0}")] Pep440VersionError(VersionParseError), - /// Invalid VersionSpecifier #[error(transparent)] Pep440Error(#[from] VersionSpecifiersParseError), - /// Invalid Requirement #[error(transparent)] Pep508Error(#[from] Pep508Error), #[error(transparent)] @@ -86,20 +64,20 @@ pub enum Error { /// From impl Metadata23 { /// Parse the [`Metadata23`] from a `METADATA` file, as included in a built distribution (wheel). - pub fn parse_metadata(content: &[u8]) -> Result { + pub fn parse_metadata(content: &[u8]) -> Result { let headers = Headers::parse(content)?; let name = PackageName::new( headers .get_first_value("Name") - .ok_or(Error::FieldNotFound("Name"))?, + .ok_or(MetadataError::FieldNotFound("Name"))?, )?; let version = Version::from_str( &headers .get_first_value("Version") - .ok_or(Error::FieldNotFound("Version"))?, + .ok_or(MetadataError::FieldNotFound("Version"))?, ) - .map_err(Error::Pep440VersionError)?; + .map_err(MetadataError::Pep440VersionError)?; let requires_dist = headers .get_all_values("Requires-Dist") .map(|requires_dist| { @@ -135,28 +113,28 @@ impl Metadata23 { /// Read the [`Metadata23`] from a source distribution's `PKG-INFO` file, if it uses Metadata 2.2 /// or later _and_ none of the required fields (`Requires-Python`, `Requires-Dist`, and /// `Provides-Extra`) are marked as dynamic. - pub fn parse_pkg_info(content: &[u8]) -> Result { + pub fn parse_pkg_info(content: &[u8]) -> Result { let headers = Headers::parse(content)?; // To rely on a source distribution's `PKG-INFO` file, the `Metadata-Version` field must be // present and set to a value of at least `2.2`. let metadata_version = headers .get_first_value("Metadata-Version") - .ok_or(Error::FieldNotFound("Metadata-Version"))?; + .ok_or(MetadataError::FieldNotFound("Metadata-Version"))?; // Parse the version into (major, minor). let (major, minor) = parse_version(&metadata_version)?; if (major, minor) < (2, 2) || (major, minor) >= (3, 0) { - return Err(Error::UnsupportedMetadataVersion(metadata_version)); + return Err(MetadataError::UnsupportedMetadataVersion(metadata_version)); } // If any of the fields we need are marked as dynamic, we can't use the `PKG-INFO` file. let dynamic = headers.get_all_values("Dynamic").collect::>(); for field in dynamic { match field.as_str() { - "Requires-Python" => return Err(Error::DynamicField("Requires-Python")), - "Requires-Dist" => return Err(Error::DynamicField("Requires-Dist")), - "Provides-Extra" => return Err(Error::DynamicField("Provides-Extra")), + "Requires-Python" => return Err(MetadataError::DynamicField("Requires-Python")), + "Requires-Dist" => return Err(MetadataError::DynamicField("Requires-Dist")), + "Provides-Extra" => return Err(MetadataError::DynamicField("Provides-Extra")), _ => (), } } @@ -165,14 +143,14 @@ impl Metadata23 { let name = PackageName::new( headers .get_first_value("Name") - .ok_or(Error::FieldNotFound("Name"))?, + .ok_or(MetadataError::FieldNotFound("Name"))?, )?; let version = Version::from_str( &headers .get_first_value("Version") - .ok_or(Error::FieldNotFound("Version"))?, + .ok_or(MetadataError::FieldNotFound("Version"))?, ) - .map_err(Error::Pep440VersionError)?; + .map_err(MetadataError::Pep440VersionError)?; // The remaining fields are required to be present. let requires_dist = headers @@ -208,29 +186,31 @@ impl Metadata23 { } /// Extract the metadata from a `pyproject.toml` file, as specified in PEP 621. - pub fn parse_pyproject_toml(contents: &str) -> Result { + pub fn parse_pyproject_toml(contents: &str) -> Result { let pyproject_toml: PyProjectToml = toml::from_str(contents)?; let project = pyproject_toml .project - .ok_or(Error::FieldNotFound("project"))?; + .ok_or(MetadataError::FieldNotFound("project"))?; // If any of the fields we need were declared as dynamic, we can't use the `pyproject.toml` file. let dynamic = project.dynamic.unwrap_or_default(); for field in dynamic { match field.as_str() { - "dependencies" => return Err(Error::DynamicField("dependencies")), + "dependencies" => return Err(MetadataError::DynamicField("dependencies")), "optional-dependencies" => { - return Err(Error::DynamicField("optional-dependencies")) + return Err(MetadataError::DynamicField("optional-dependencies")) } - "requires-python" => return Err(Error::DynamicField("requires-python")), - "version" => return Err(Error::DynamicField("version")), + "requires-python" => return Err(MetadataError::DynamicField("requires-python")), + "version" => return Err(MetadataError::DynamicField("version")), _ => (), } } let name = project.name; - let version = project.version.ok_or(Error::FieldNotFound("version"))?; + let version = project + .version + .ok_or(MetadataError::FieldNotFound("version"))?; let requires_python = project.requires_python.map(VersionSpecifiers::from); // Extract the requirements. @@ -309,28 +289,31 @@ pub struct Metadata10 { impl Metadata10 { /// Parse the [`Metadata10`] from a `PKG-INFO` file, as included in a source distribution. - pub fn parse_pkg_info(content: &[u8]) -> Result { + pub fn parse_pkg_info(content: &[u8]) -> Result { let headers = Headers::parse(content)?; let name = PackageName::new( headers .get_first_value("Name") - .ok_or(Error::FieldNotFound("Name"))?, + .ok_or(MetadataError::FieldNotFound("Name"))?, )?; Ok(Self { name }) } } /// Parse a `Metadata-Version` field into a (major, minor) tuple. -fn parse_version(metadata_version: &str) -> Result<(u8, u8), Error> { - let (major, minor) = metadata_version - .split_once('.') - .ok_or(Error::InvalidMetadataVersion(metadata_version.to_string()))?; +fn parse_version(metadata_version: &str) -> Result<(u8, u8), MetadataError> { + let (major, minor) = + metadata_version + .split_once('.') + .ok_or(MetadataError::InvalidMetadataVersion( + metadata_version.to_string(), + ))?; let major = major .parse::() - .map_err(|_| Error::InvalidMetadataVersion(metadata_version.to_string()))?; + .map_err(|_| MetadataError::InvalidMetadataVersion(metadata_version.to_string()))?; let minor = minor .parse::() - .map_err(|_| Error::InvalidMetadataVersion(metadata_version.to_string()))?; + .map_err(|_| MetadataError::InvalidMetadataVersion(metadata_version.to_string()))?; Ok((major, minor)) } @@ -373,7 +356,7 @@ mod tests { use pep440_rs::Version; use uv_normalize::PackageName; - use crate::Error; + use crate::MetadataError; use super::Metadata23; @@ -381,11 +364,11 @@ mod tests { fn test_parse_metadata() { let s = "Metadata-Version: 1.0"; let meta = Metadata23::parse_metadata(s.as_bytes()); - assert!(matches!(meta, Err(Error::FieldNotFound("Name")))); + assert!(matches!(meta, Err(MetadataError::FieldNotFound("Name")))); let s = "Metadata-Version: 1.0\nName: asdf"; let meta = Metadata23::parse_metadata(s.as_bytes()); - assert!(matches!(meta, Err(Error::FieldNotFound("Version")))); + assert!(matches!(meta, Err(MetadataError::FieldNotFound("Version")))); let s = "Metadata-Version: 1.0\nName: asdf\nVersion: 1.0"; let meta = Metadata23::parse_metadata(s.as_bytes()).unwrap(); @@ -404,22 +387,25 @@ mod tests { let s = "Metadata-Version: 1.0\nName: =?utf-8?q?=C3=A4_space?= \nVersion: 1.0"; let meta = Metadata23::parse_metadata(s.as_bytes()); - assert!(matches!(meta, Err(Error::InvalidName(_)))); + assert!(matches!(meta, Err(MetadataError::InvalidName(_)))); } #[test] fn test_parse_pkg_info() { let s = "Metadata-Version: 2.1"; let meta = Metadata23::parse_pkg_info(s.as_bytes()); - assert!(matches!(meta, Err(Error::UnsupportedMetadataVersion(_)))); + assert!(matches!( + meta, + Err(MetadataError::UnsupportedMetadataVersion(_)) + )); let s = "Metadata-Version: 2.2\nName: asdf"; let meta = Metadata23::parse_pkg_info(s.as_bytes()); - assert!(matches!(meta, Err(Error::FieldNotFound("Version")))); + assert!(matches!(meta, Err(MetadataError::FieldNotFound("Version")))); let s = "Metadata-Version: 2.3\nName: asdf"; let meta = Metadata23::parse_pkg_info(s.as_bytes()); - assert!(matches!(meta, Err(Error::FieldNotFound("Version")))); + assert!(matches!(meta, Err(MetadataError::FieldNotFound("Version")))); let s = "Metadata-Version: 2.3\nName: asdf\nVersion: 1.0"; let meta = Metadata23::parse_pkg_info(s.as_bytes()).unwrap(); @@ -428,7 +414,7 @@ mod tests { let s = "Metadata-Version: 2.3\nName: asdf\nVersion: 1.0\nDynamic: Requires-Dist"; let meta = Metadata23::parse_pkg_info(s.as_bytes()).unwrap_err(); - assert!(matches!(meta, Error::DynamicField("Requires-Dist"))); + assert!(matches!(meta, MetadataError::DynamicField("Requires-Dist"))); let s = "Metadata-Version: 2.3\nName: asdf\nVersion: 1.0\nRequires-Dist: foo"; let meta = Metadata23::parse_pkg_info(s.as_bytes()).unwrap(); @@ -444,7 +430,7 @@ mod tests { name = "asdf" "#; let meta = Metadata23::parse_pyproject_toml(s); - assert!(matches!(meta, Err(Error::FieldNotFound("version")))); + assert!(matches!(meta, Err(MetadataError::FieldNotFound("version")))); let s = r#" [project] @@ -452,7 +438,7 @@ mod tests { dynamic = ["version"] "#; let meta = Metadata23::parse_pyproject_toml(s); - assert!(matches!(meta, Err(Error::DynamicField("version")))); + assert!(matches!(meta, Err(MetadataError::DynamicField("version")))); let s = r#" [project] diff --git a/crates/uv-client/src/error.rs b/crates/uv-client/src/error.rs index 38bc66bad..d5f56b1f0 100644 --- a/crates/uv-client/src/error.rs +++ b/crates/uv-client/src/error.rs @@ -146,7 +146,11 @@ pub enum ErrorKind { /// The metadata file could not be parsed. #[error("Couldn't parse metadata of {0} from {1}")] - MetadataParseError(WheelFilename, String, #[source] Box), + MetadataParseError( + WheelFilename, + String, + #[source] Box, + ), /// The metadata file was not found in the wheel. #[error("Metadata file `{0}` was not found in {1}")] diff --git a/crates/uv-distribution/src/error.rs b/crates/uv-distribution/src/error.rs index 95b313b68..fe61b95da 100644 --- a/crates/uv-distribution/src/error.rs +++ b/crates/uv-distribution/src/error.rs @@ -48,7 +48,7 @@ pub enum Error { metadata: PackageName, }, #[error("Failed to parse metadata from built wheel")] - Metadata(#[from] pypi_types::Error), + Metadata(#[from] pypi_types::MetadataError), #[error("Failed to read `dist-info` metadata from built wheel")] DistInfo(#[from] install_wheel_rs::Error), #[error("Failed to read zip archive from built wheel")] @@ -62,11 +62,11 @@ pub enum Error { #[error("The source distribution is missing a `PKG-INFO` file")] MissingPkgInfo, #[error("The source distribution does not support static metadata in `PKG-INFO`")] - DynamicPkgInfo(#[source] pypi_types::Error), + DynamicPkgInfo(#[source] pypi_types::MetadataError), #[error("The source distribution is missing a `pyproject.toml` file")] MissingPyprojectToml, #[error("The source distribution does not support static metadata in `pyproject.toml`")] - DynamicPyprojectToml(#[source] pypi_types::Error), + DynamicPyprojectToml(#[source] pypi_types::MetadataError), #[error("Unsupported scheme in URL: {0}")] UnsupportedScheme(String),