Skip to content

Commit

Permalink
Avoid failing when deserializing unknown tags
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Jan 15, 2025
1 parent a7f13e0 commit 8faf04d
Show file tree
Hide file tree
Showing 10 changed files with 242 additions and 129 deletions.
6 changes: 5 additions & 1 deletion crates/uv-distribution-filename/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,13 @@ impl Display for DistFilename {
#[cfg(test)]
mod tests {
use crate::WheelFilename;
use uv_platform_tags::{AbiTag, LanguageTag, PlatformTag};

#[test]
fn wheel_filename_size() {
assert_eq!(size_of::<WheelFilename>(), 48);
assert_eq!(size_of::<WheelFilename>(), 72);
assert_eq!(size_of::<LanguageTag>(), 16);
assert_eq!(size_of::<AbiTag>(), 16);
assert_eq!(size_of::<PlatformTag>(), 16);
}
}
9 changes: 6 additions & 3 deletions crates/uv-distribution-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1337,14 +1337,17 @@ impl Identifier for BuildableSource<'_> {

#[cfg(test)]
mod test {
use crate::{BuiltDist, Dist, RemoteSource, SourceDist, UrlString};
use crate::{
BuiltDist, DirectUrlBuiltDist, Dist, PathBuiltDist, RegistryBuiltDist, RemoteSource,
SourceDist, UrlString,
};
use url::Url;

/// Ensure that we don't accidentally grow the `Dist` sizes.
#[test]
fn dist_size() {
assert!(size_of::<Dist>() <= 200, "{}", size_of::<Dist>());
assert!(size_of::<BuiltDist>() <= 200, "{}", size_of::<BuiltDist>());
assert!(size_of::<Dist>() <= 208, "{}", size_of::<Dist>());
assert!(size_of::<BuiltDist>() <= 208, "{}", size_of::<BuiltDist>());
assert!(
size_of::<SourceDist>() <= 176,
"{}",
Expand Down
10 changes: 5 additions & 5 deletions crates/uv-distribution-types/src/prioritized_distribution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -523,20 +523,20 @@ impl PrioritizedDist {
}

/// Returns the set of all Python tags for the distribution.
pub fn python_tags(&self) -> BTreeSet<LanguageTag> {
pub fn python_tags(&self) -> BTreeSet<&LanguageTag> {
self.0
.wheels
.iter()
.flat_map(|(wheel, _)| wheel.filename.python_tags().iter().copied())
.flat_map(|(wheel, _)| wheel.filename.python_tags().iter())
.collect()
}

/// Returns the set of all ABI tags for the distribution.
pub fn abi_tags(&self) -> BTreeSet<AbiTag> {
pub fn abi_tags(&self) -> BTreeSet<&AbiTag> {
self.0
.wheels
.iter()
.flat_map(|(wheel, _)| wheel.filename.abi_tags().iter().copied())
.flat_map(|(wheel, _)| wheel.filename.abi_tags().iter())
.collect()
}

Expand All @@ -547,7 +547,7 @@ impl PrioritizedDist {
for (wheel, _) in &self.0.wheels {
for wheel_py in wheel.filename.python_tags() {
for wheel_abi in wheel.filename.abi_tags() {
if tags.is_compatible_abi(*wheel_py, *wheel_abi) {
if tags.is_compatible_abi(wheel_py, wheel_abi) {
candidates.extend(wheel.filename.platform_tags().iter());
}
}
Expand Down
69 changes: 40 additions & 29 deletions crates/uv-platform-tags/src/abi_tag.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use std::fmt::Formatter;
use std::str::FromStr;

use uv_small_str::SmallString;

/// A tag to represent the ABI compatibility of a Python distribution.
///
/// This is the second segment in the wheel filename, following the language tag. For example,
/// in `cp39-none-manylinux_2_24_x86_64.whl`, the ABI tag is `none`.
#[derive(
Debug,
Copy,
Clone,
Eq,
PartialEq,
Expand Down Expand Up @@ -41,11 +42,13 @@ pub enum AbiTag {
},
/// Ex) `pyston_23_x86_64_linux_gnu`
Pyston { implementation_version: (u8, u8) },
/// Ex) `pypy_73`
Unknown { tag: SmallString },
}

impl AbiTag {
/// Return a pretty string representation of the ABI tag.
pub fn pretty(self) -> Option<String> {
pub fn pretty(&self) -> Option<String> {
match self {
AbiTag::None => None,
AbiTag::Abi3 => None,
Expand All @@ -59,6 +62,7 @@ impl AbiTag {
Some(format!("GraalPy {}.{}", python_version.0, python_version.1))
}
AbiTag::Pyston { .. } => Some("Pyston".to_string()),
AbiTag::Unknown { .. } => None,
}
}
}
Expand Down Expand Up @@ -103,16 +107,14 @@ impl std::fmt::Display for AbiTag {
} => {
write!(f, "pyston_{impl_major}{impl_minor}_x86_64_linux_gnu")
}
Self::Unknown { tag } => write!(f, "{tag}"),
}
}
}

impl FromStr for AbiTag {
type Err = ParseAbiTagError;

impl AbiTag {
/// Parse an [`AbiTag`] from a string.
#[allow(clippy::cast_possible_truncation)]
fn from_str(s: &str) -> Result<Self, Self::Err> {
fn parse(s: &str) -> Result<Self, ParseAbiTagError> {
/// Parse a Python version from a string (e.g., convert `39` into `(3, 9)`).
fn parse_python_version(
version_str: &str,
Expand Down Expand Up @@ -265,6 +267,14 @@ impl FromStr for AbiTag {
}
}

impl FromStr for AbiTag {
type Err = ParseAbiTagError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(AbiTag::parse(s).unwrap_or_else(|_| AbiTag::Unknown { tag: s.into() }))
}
}

#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum ParseAbiTagError {
#[error("Unknown ABI tag format: {0}")]
Expand Down Expand Up @@ -318,19 +328,17 @@ pub enum ParseAbiTagError {

#[cfg(test)]
mod tests {
use std::str::FromStr;

use crate::abi_tag::{AbiTag, ParseAbiTagError};

#[test]
fn none_abi() {
assert_eq!(AbiTag::from_str("none"), Ok(AbiTag::None));
assert_eq!(AbiTag::parse("none"), Ok(AbiTag::None));
assert_eq!(AbiTag::None.to_string(), "none");
}

#[test]
fn abi3() {
assert_eq!(AbiTag::from_str("abi3"), Ok(AbiTag::Abi3));
assert_eq!(AbiTag::parse("abi3"), Ok(AbiTag::Abi3));
assert_eq!(AbiTag::Abi3.to_string(), "abi3");
}

Expand All @@ -340,25 +348,25 @@ mod tests {
gil_disabled: false,
python_version: (3, 9),
};
assert_eq!(AbiTag::from_str("cp39"), Ok(tag));
assert_eq!(AbiTag::parse("cp39").as_ref(), Ok(&tag));
assert_eq!(tag.to_string(), "cp39");

let tag = AbiTag::CPython {
gil_disabled: false,
python_version: (3, 7),
};
assert_eq!(AbiTag::from_str("cp37m"), Ok(tag));
assert_eq!(AbiTag::parse("cp37m").as_ref(), Ok(&tag));
assert_eq!(tag.to_string(), "cp37m");

let tag = AbiTag::CPython {
gil_disabled: true,
python_version: (3, 13),
};
assert_eq!(AbiTag::from_str("cp313t"), Ok(tag));
assert_eq!(AbiTag::parse("cp313t").as_ref(), Ok(&tag));
assert_eq!(tag.to_string(), "cp313t");

assert_eq!(
AbiTag::from_str("cpXY"),
AbiTag::parse("cpXY"),
Err(ParseAbiTagError::MissingMajorVersion {
implementation: "CPython",
tag: "cpXY".to_string()
Expand All @@ -372,25 +380,25 @@ mod tests {
python_version: (3, 9),
implementation_version: (7, 3),
};
assert_eq!(AbiTag::from_str("pypy39_pp73"), Ok(tag));
assert_eq!(AbiTag::parse("pypy39_pp73").as_ref(), Ok(&tag));
assert_eq!(tag.to_string(), "pypy39_pp73");

assert_eq!(
AbiTag::from_str("pypy39"),
AbiTag::parse("pypy39"),
Err(ParseAbiTagError::InvalidFormat {
implementation: "PyPy",
tag: "pypy39".to_string()
})
);
assert_eq!(
AbiTag::from_str("pypy39_73"),
AbiTag::parse("pypy39_73"),
Err(ParseAbiTagError::InvalidFormat {
implementation: "PyPy",
tag: "pypy39_73".to_string()
})
);
assert_eq!(
AbiTag::from_str("pypy39_ppXY"),
AbiTag::parse("pypy39_ppXY"),
Err(ParseAbiTagError::InvalidImplMajorVersion {
implementation: "PyPy",
tag: "pypy39_ppXY".to_string()
Expand All @@ -405,27 +413,27 @@ mod tests {
implementation_version: (2, 40),
};
assert_eq!(
AbiTag::from_str("graalpy310_graalpy240_310_native"),
Ok(tag)
AbiTag::parse("graalpy310_graalpy240_310_native").as_ref(),
Ok(&tag)
);
assert_eq!(tag.to_string(), "graalpy310_graalpy240_310_native");

assert_eq!(
AbiTag::from_str("graalpy310"),
AbiTag::parse("graalpy310"),
Err(ParseAbiTagError::InvalidFormat {
implementation: "GraalPy",
tag: "graalpy310".to_string()
})
);
assert_eq!(
AbiTag::from_str("graalpy310_240"),
AbiTag::parse("graalpy310_240"),
Err(ParseAbiTagError::InvalidFormat {
implementation: "GraalPy",
tag: "graalpy310_240".to_string()
})
);
assert_eq!(
AbiTag::from_str("graalpy310_graalpyXY"),
AbiTag::parse("graalpy310_graalpyXY"),
Err(ParseAbiTagError::InvalidFormat {
implementation: "GraalPy",
tag: "graalpy310_graalpyXY".to_string()
Expand All @@ -438,18 +446,21 @@ mod tests {
let tag = AbiTag::Pyston {
implementation_version: (2, 3),
};
assert_eq!(AbiTag::from_str("pyston_23_x86_64_linux_gnu"), Ok(tag));
assert_eq!(
AbiTag::parse("pyston_23_x86_64_linux_gnu").as_ref(),
Ok(&tag)
);
assert_eq!(tag.to_string(), "pyston_23_x86_64_linux_gnu");

assert_eq!(
AbiTag::from_str("pyston23_x86_64_linux_gnu"),
AbiTag::parse("pyston23_x86_64_linux_gnu"),
Err(ParseAbiTagError::InvalidFormat {
implementation: "Pyston",
tag: "pyston23_x86_64_linux_gnu".to_string()
})
);
assert_eq!(
AbiTag::from_str("pyston_XY_x86_64_linux_gnu"),
AbiTag::parse("pyston_XY_x86_64_linux_gnu"),
Err(ParseAbiTagError::InvalidImplMajorVersion {
implementation: "Pyston",
tag: "pyston_XY_x86_64_linux_gnu".to_string()
Expand All @@ -460,11 +471,11 @@ mod tests {
#[test]
fn unknown_abi() {
assert_eq!(
AbiTag::from_str("unknown"),
AbiTag::parse("unknown"),
Err(ParseAbiTagError::UnknownFormat("unknown".to_string()))
);
assert_eq!(
AbiTag::from_str(""),
AbiTag::parse(""),
Err(ParseAbiTagError::UnknownFormat(String::new()))
);
}
Expand Down
Loading

0 comments on commit 8faf04d

Please sign in to comment.