Clean up Error enum in Metadata23 (#2835)

## Summary

Rename, and remove a bunch of unused variants.
This commit is contained in:
Charlie Marsh 2024-04-05 10:40:33 -04:00 committed by GitHub
parent 2f386ef440
commit a0b8d1a994
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 56 additions and 66 deletions

View file

@ -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<String>),
/// 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 <https://github.com/PyO3/python-pkginfo-rs/blob/d719988323a0cfea86d4737116d7917f30e819e2/src/metadata.rs#LL78C2-L91C26>
impl Metadata23 {
/// Parse the [`Metadata23`] from a `METADATA` file, as included in a built distribution (wheel).
pub fn parse_metadata(content: &[u8]) -> Result<Self, Error> {
pub fn parse_metadata(content: &[u8]) -> Result<Self, MetadataError> {
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<Self, Error> {
pub fn parse_pkg_info(content: &[u8]) -> Result<Self, MetadataError> {
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::<Vec<_>>();
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<Self, Error> {
pub fn parse_pyproject_toml(contents: &str) -> Result<Self, MetadataError> {
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<Self, Error> {
pub fn parse_pkg_info(content: &[u8]) -> Result<Self, MetadataError> {
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::<u8>()
.map_err(|_| Error::InvalidMetadataVersion(metadata_version.to_string()))?;
.map_err(|_| MetadataError::InvalidMetadataVersion(metadata_version.to_string()))?;
let minor = minor
.parse::<u8>()
.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?= <x@y.org>\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]