From 8a807094e998b2cc5a42b157a6d1800aff19687a Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 7 Mar 2024 19:59:53 -0800 Subject: [PATCH] Encapsulate header parsing for metadata files (#2295) --- crates/pypi-types/src/metadata.rs | 60 ++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/crates/pypi-types/src/metadata.rs b/crates/pypi-types/src/metadata.rs index 3ab5620a8..da563db46 100644 --- a/crates/pypi-types/src/metadata.rs +++ b/crates/pypi-types/src/metadata.rs @@ -76,24 +76,7 @@ pub enum Error { impl Metadata21 { /// Parse distribution metadata from metadata bytes pub fn parse(content: &[u8]) -> Result { - let (headers, _) = mailparse::parse_headers(content)?; - - let get_first_value = |name| { - headers.get_first_header(name).and_then(|header| { - let value = header.get_value(); - if value == "UNKNOWN" { - None - } else { - Some(value) - } - }) - }; - let get_all_values = |name| { - headers - .get_all_values(name) - .into_iter() - .filter(|value| value != "UNKNOWN") - }; + let headers = Headers::parse(content)?; let metadata_version = headers .get_first_value("Metadata-Version") @@ -109,17 +92,20 @@ impl Metadata21 { .ok_or(Error::FieldNotFound("Version"))?, ) .map_err(Error::Pep440VersionError)?; - let requires_dist = get_all_values("Requires-Dist") + let requires_dist = headers + .get_all_values("Requires-Dist") .map(|requires_dist| { LenientRequirement::from_str(&requires_dist).map(Requirement::from) }) .collect::, _>>()?; - let requires_python = get_first_value("Requires-Python") + let requires_python = headers + .get_first_value("Requires-Python") .map(|requires_python| { LenientVersionSpecifiers::from_str(&requires_python).map(VersionSpecifiers::from) }) .transpose()?; - let provides_extras = get_all_values("Provides-Extra") + let provides_extras = headers + .get_all_values("Provides-Extra") .filter_map(|provides_extra| match ExtraName::new(provides_extra) { Ok(extra_name) => Some(extra_name), Err(err) => { @@ -147,6 +133,38 @@ impl FromStr for Metadata21 { } } +/// The headers of a distribution metadata file. +#[derive(Debug)] +struct Headers<'a>(Vec>); + +impl<'a> Headers<'a> { + /// Parse the headers from the given metadata file content. + fn parse(content: &'a [u8]) -> Result { + let (headers, _) = mailparse::parse_headers(content)?; + Ok(Self(headers)) + } + + /// Return the first value associated with the header with the given name. + fn get_first_value(&self, name: &str) -> Option { + self.0.get_first_header(name).and_then(|header| { + let value = header.get_value(); + if value == "UNKNOWN" { + None + } else { + Some(value) + } + }) + } + + /// Return all values associated with the header with the given name. + fn get_all_values(&self, name: &str) -> impl Iterator { + self.0 + .get_all_values(name) + .into_iter() + .filter(|value| value != "UNKNOWN") + } +} + #[cfg(test)] mod tests { use std::str::FromStr;