From ac906c6dc7e6edc6513dee7bf492005e6ef8d022 Mon Sep 17 00:00:00 2001 From: Zanie Date: Wed, 1 Nov 2023 11:38:22 -0500 Subject: [PATCH] Add `PackageName::validate` --- crates/puffin-package/src/package_name.rs | 50 ++++++++++++++++++++--- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/crates/puffin-package/src/package_name.rs b/crates/puffin-package/src/package_name.rs index f13c8e92a..2231f5ea8 100644 --- a/crates/puffin-package/src/package_name.rs +++ b/crates/puffin-package/src/package_name.rs @@ -1,11 +1,11 @@ use std::fmt; use std::fmt::{Display, Formatter}; +use crate::dist_info_name::DistInfoName; +use anyhow::{anyhow, Result}; use once_cell::sync::Lazy; use regex::Regex; -use crate::dist_info_name::DistInfoName; - #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct PackageName(String); @@ -23,20 +23,37 @@ impl Display for PackageName { } static NAME_NORMALIZE: Lazy = Lazy::new(|| Regex::new(r"[-_.]+").unwrap()); +static NAME_VALIDATE: Lazy = + Lazy::new(|| Regex::new(r"(?i)^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$").unwrap()); +/// A package name. +/// +/// See: +/// - +/// - +/// - impl PackageName { - /// Create a normalized representation of a package name. + /// Create a normalized package name without validation. /// /// Converts the name to lowercase and collapses any run of the characters `-`, `_` and `.` /// down to a single `-`, e.g., `---`, `.`, and `__` all get converted to just `-`. - /// - /// See: pub fn normalize(name: impl AsRef) -> Self { // TODO(charlie): Avoid allocating in the common case (when no normalization is required). let mut normalized = NAME_NORMALIZE.replace_all(name.as_ref(), "-").to_string(); normalized.make_ascii_lowercase(); Self(normalized) } + + /// Create a validated, normalized extra name. + pub fn validate(name: impl AsRef) -> Result { + if NAME_VALIDATE.is_match(name.as_ref()) { + Ok(Self::normalize(name)) + } else { + Err(anyhow!( + "Package names must start and end with a letter or digit and may only contain -, _, ., and alphanumeric characters" + )) + } + } } impl AsRef for PackageName { @@ -86,4 +103,27 @@ mod tests { "friendly-bard" ); } + + #[test] + fn validate() { + // Unchanged + assert_eq!( + PackageName::validate("friendly-bard").unwrap().as_ref(), + "friendly-bard" + ); + assert_eq!(PackageName::validate("1okay").unwrap().as_ref(), "1okay"); + assert_eq!(PackageName::validate("okay2").unwrap().as_ref(), "okay2"); + // Normalizes + assert_eq!( + PackageName::validate("Friendly-Bard").unwrap().as_ref(), + "friendly-bard" + ); + // Failures... + assert!(PackageName::validate(" starts-with-space").is_err()); + assert!(PackageName::validate("-starts-with-dash").is_err()); + assert!(PackageName::validate("ends-with-dash-").is_err()); + assert!(PackageName::validate("ends-with-space ").is_err()); + assert!(PackageName::validate("includes!invalid-char").is_err()); + assert!(PackageName::validate("space in middle").is_err()); + } }