diff --git a/src/models/krate.rs b/src/models/krate.rs index 146faad160..b0e4a27c40 100644 --- a/src/models/krate.rs +++ b/src/models/krate.rs @@ -250,24 +250,29 @@ impl Crate { } fn valid_ident(name: &str) -> bool { - if name.is_empty() { - return false; - } - name.chars().next().unwrap().is_alphabetic() + Self::valid_feature_name(name) + && name.chars() + .nth(0) + .map(char::is_alphabetic) + .unwrap_or(false) + } + + pub fn valid_feature_name(name: &str) -> bool { + !name.is_empty() && name.chars() .all(|c| c.is_alphanumeric() || c == '_' || c == '-') && name.chars().all(|c| c.is_ascii()) } - pub fn valid_feature_name(name: &str) -> bool { + pub fn valid_feature(name: &str) -> bool { let mut parts = name.split('/'); match parts.next() { - Some(part) if !Crate::valid_ident(part) => return false, + Some(part) if !Crate::valid_feature_name(part) => return false, None => return false, _ => {} } match parts.next() { - Some(part) if !Crate::valid_ident(part) => return false, + Some(part) if !Crate::valid_feature_name(part) => return false, _ => {} } parts.next().is_none() diff --git a/src/tests/krate.rs b/src/tests/krate.rs index de46ad4aea..3726852489 100644 --- a/src/tests/krate.rs +++ b/src/tests/krate.rs @@ -753,11 +753,12 @@ fn new_krate_bad_name() { #[test] fn valid_feature_names() { - assert!(Crate::valid_feature_name("foo")); - assert!(!Crate::valid_feature_name("")); - assert!(!Crate::valid_feature_name("/")); - assert!(!Crate::valid_feature_name("%/%")); - assert!(Crate::valid_feature_name("a/a")); + assert!(Crate::valid_feature("foo")); + assert!(!Crate::valid_feature("")); + assert!(!Crate::valid_feature("/")); + assert!(!Crate::valid_feature("%/%")); + assert!(Crate::valid_feature("a/a")); + assert!(Crate::valid_feature("32-column-tables")); } #[test] diff --git a/src/views/krate_publish.rs b/src/views/krate_publish.rs index dd8f045fd3..051d6f16be 100644 --- a/src/views/krate_publish.rs +++ b/src/views/krate_publish.rs @@ -18,7 +18,7 @@ pub struct NewCrate { pub name: CrateName, pub vers: CrateVersion, pub deps: Vec, - pub features: HashMap>, + pub features: HashMap>, pub authors: Vec, pub description: Option, pub homepage: Option, @@ -51,6 +51,8 @@ pub struct CategoryList(pub Vec); pub struct Category(pub String); #[derive(Serialize, Debug, Deref)] pub struct Feature(pub String); +#[derive(PartialEq, Eq, Hash, Serialize, Debug, Deref)] +pub struct FeatureName(pub String); #[derive(Serialize, Deserialize, Debug)] pub struct CrateDependency { @@ -102,6 +104,20 @@ impl<'de> Deserialize<'de> for Keyword { } } +impl<'de> Deserialize<'de> for FeatureName { + fn deserialize>(d: D) -> Result { + let s = String::deserialize(d)?; + if !Crate::valid_feature_name(&s) { + let value = de::Unexpected::Str(&s); + let expected = "a valid feature name containing only letters, \ + numbers, hyphens, or underscores"; + Err(de::Error::invalid_value(value, &expected)) + } else { + Ok(FeatureName(s)) + } + } +} + impl<'de> Deserialize<'de> for Feature { fn deserialize>(d: D) -> Result { let s = String::deserialize(d)?;