Validate package and extra name (#290)

`PackageName` and `ExtraName` can now only be constructed from valid
names. They share the same rules, so i gave them the same
implementation. Constructors are split between `new` (owned) and
`from_str` (borrowed), with the owned version avoiding allocations.

Closes #279

---------

Co-authored-by: Zanie <contact@zanie.dev>
This commit is contained in:
konsti 2023-11-06 11:04:31 +01:00 committed by GitHub
parent ea28b3d0d3
commit 81f380b10e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 258 additions and 171 deletions

View file

@ -11,7 +11,7 @@
//! let marker = r#"requests [security,tests] >= 2.8.1, == 2.8.* ; python_version > "3.8""#;
//! let dependency_specification = Requirement::from_str(marker).unwrap();
//! assert_eq!(dependency_specification.name.as_ref(), "requests");
//! assert_eq!(dependency_specification.extras, Some(vec![ExtraName::new("security"), ExtraName::new("tests")]));
//! assert_eq!(dependency_specification.extras, Some(vec![ExtraName::from_str("security").unwrap(), ExtraName::from_str("tests").unwrap()]));
//! ```
#![deny(missing_docs)]
@ -63,21 +63,13 @@ pub struct Pep508Error {
#[derive(Debug, Error, Clone, Eq, PartialEq)]
pub enum Pep508ErrorSource {
/// An error from our parser
#[error("{0}")]
String(String),
/// A url parsing error
#[error(transparent)]
UrlError(#[from] url::ParseError),
}
impl Display for Pep508ErrorSource {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Pep508ErrorSource::String(string) => string.fmt(f),
Pep508ErrorSource::UrlError(parse_err) => parse_err.fmt(f),
}
}
}
impl Display for Pep508Error {
/// Pretty formatting with underline
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
@ -568,7 +560,10 @@ fn parse_name(chars: &mut CharIter) -> Result<PackageName, Pep508Error> {
});
}
}
Some(_) | None => return Ok(PackageName::new(name)),
Some(_) | None => {
return Ok(PackageName::new(name)
.expect("`PackageName` validation should match PEP 508 parsing"));
}
}
}
}
@ -641,10 +636,16 @@ fn parse_extras(chars: &mut CharIter) -> Result<Option<Vec<ExtraName>>, Pep508Er
// end or next identifier?
match chars.next() {
Some((_, ',')) => {
extras.push(ExtraName::new(buffer));
extras.push(
ExtraName::new(buffer)
.expect("`ExtraName` validation should match PEP 508 parsing"),
);
}
Some((_, ']')) => {
extras.push(ExtraName::new(buffer));
extras.push(
ExtraName::new(buffer)
.expect("`ExtraName` validation should match PEP 508 parsing"),
);
break;
}
Some((pos, other)) => {
@ -944,8 +945,11 @@ mod tests {
let requests = Requirement::from_str(input).unwrap();
assert_eq!(input, requests.to_string());
let expected = Requirement {
name: PackageName::new("requests"),
extras: Some(vec![ExtraName::new("security"), ExtraName::new("tests")]),
name: PackageName::from_str("requests").unwrap(),
extras: Some(vec![
ExtraName::from_str("security").unwrap(),
ExtraName::from_str("tests").unwrap(),
]),
version_or_url: Some(VersionOrUrl::VersionSpecifier(
[
VersionSpecifier::new(
@ -1086,7 +1090,7 @@ mod tests {
#[test]
fn error_extras1() {
let numpy = Requirement::from_str("black[d]").unwrap();
assert_eq!(numpy.extras, Some(vec![ExtraName::new("d")]));
assert_eq!(numpy.extras, Some(vec![ExtraName::from_str("d").unwrap()]));
}
#[test]
@ -1094,7 +1098,10 @@ mod tests {
let numpy = Requirement::from_str("black[d,jupyter]").unwrap();
assert_eq!(
numpy.extras,
Some(vec![ExtraName::new("d"), ExtraName::new("jupyter")])
Some(vec![
ExtraName::from_str("d").unwrap(),
ExtraName::from_str("jupyter").unwrap()
])
);
}
@ -1141,7 +1148,7 @@ mod tests {
.unwrap();
let url = "https://github.com/pypa/pip/archive/1.3.1.zip#sha1=da9234ee9982d4bbb3c72346a6de940a148ea686";
let expected = Requirement {
name: PackageName::new("pip"),
name: PackageName::from_str("pip").unwrap(),
extras: None,
marker: None,
version_or_url: Some(VersionOrUrl::Url(Url::parse(url).unwrap())),