use crate::prelude::*;
#[derive(Debug, Clone, DeserializeFromStr, Derivative)]
#[derivative(Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct PackageName {
#[derivative(Hash = "ignore", PartialEq = "ignore", PartialOrd = "ignore")]
as_given: String,
normalized: String,
}
impl PackageName {
pub fn as_given(&self) -> &str {
&self.as_given
}
pub fn normalized(&self) -> &str {
&self.normalized
}
}
impl TryFrom<&str> for PackageName {
type Error = eyre::Report;
fn try_from(as_given: &str) -> Result<Self, Self::Error> {
static NAME_VALIDATE: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"(?i-u)^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$").unwrap()
});
static NAME_NORMALIZE: Lazy<Regex> =
Lazy::new(|| Regex::new(r"[-_.]").unwrap());
if !NAME_VALIDATE.is_match(as_given) {
return Err(eyre!("Invalid package name {:?}", as_given));
}
let as_given = as_given.to_owned();
let mut normalized = NAME_NORMALIZE.replace_all(&as_given, "-").to_string();
normalized.make_ascii_lowercase();
Ok(PackageName {
as_given,
normalized,
})
}
}
try_from_str_boilerplate!(PackageName);
impl Serialize for PackageName {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.as_given())
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_packagename_basics() {
let name1: PackageName = "Foo-Bar-Baz".try_into().unwrap();
assert_eq!(name1.as_given(), "Foo-Bar-Baz");
assert_eq!(name1.normalized(), "foo-bar-baz");
let name2: PackageName = "foo_bar.baz".try_into().unwrap();
assert_eq!(name2.as_given(), "foo_bar.baz");
assert_eq!(name2.normalized(), "foo-bar-baz");
assert_eq!(name1, name2);
let name3: PackageName = "foo-barbaz".try_into().unwrap();
assert_ne!(name1, name3);
}
#[test]
fn test_packagename_validation() {
let name: Result<PackageName> = "foobar baz".try_into();
assert!(name.is_err());
let name: Result<PackageName> = "foobarbaz!".parse();
assert!(name.is_err());
}
#[test]
fn test_packagename_serde() {
let direct: PackageName = "foo-bar_baz".try_into().unwrap();
let via_serde: Vec<PackageName> =
serde_json::from_str(r#"["foo_bar.baz"]"#).unwrap();
assert_eq!(via_serde[0], direct);
assert_eq!(via_serde[0].as_given(), "foo_bar.baz");
assert_eq!(via_serde[0].normalized(), "foo-bar-baz");
let bad: serde_json::Result<PackageName> =
serde_json::from_str(r#" "foo bar" "#);
assert!(bad.is_err());
}
#[test]
fn test_packagename_hash() {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
fn calculate_hash<T: Hash>(t: &T) -> u64 {
let mut s = DefaultHasher::new();
t.hash(&mut s);
s.finish()
}
let name1: PackageName = "foo_bar".try_into().unwrap();
let name2: PackageName = "foo.bar".try_into().unwrap();
let name_other: PackageName = "foobar".try_into().unwrap();
assert_eq!(calculate_hash(&name1), calculate_hash(&name2));
assert_ne!(calculate_hash(&name1), calculate_hash(&name_other));
}
}