use crate::error::CargoError;
pub const MAX_NAME_LEN: usize = 64;
#[must_use]
pub fn is_valid_name(name: &str) -> bool {
if name.is_empty() || name.len() > MAX_NAME_LEN {
return false;
}
let mut chars = name.chars();
let Some(first) = chars.next() else {
return false;
};
if !first.is_ascii_alphabetic() {
return false;
}
chars.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
}
pub fn validate_name(name: &str) -> Result<(), CargoError> {
if is_valid_name(name) {
Ok(())
} else {
Err(CargoError::InvalidName(name.to_owned()))
}
}
#[must_use]
pub fn index_path(name: &str) -> String {
let lower = name.to_ascii_lowercase();
match lower.len() {
0 => String::new(),
1 => format!("1/{lower}"),
2 => format!("2/{lower}"),
3 => format!("3/{}/{}", &lower[0..1], lower),
_ => format!("{}/{}/{}", &lower[0..2], &lower[2..4], lower),
}
}
#[cfg(test)]
mod tests {
use super::{index_path, is_valid_name, validate_name};
#[test]
fn valid_crate_names() {
assert!(is_valid_name("serde"));
assert!(is_valid_name("tokio"));
assert!(is_valid_name("abc-def"));
assert!(is_valid_name("abc_def"));
assert!(is_valid_name("A")); }
#[test]
fn invalid_crate_names() {
assert!(!is_valid_name(""));
assert!(!is_valid_name("-foo"));
assert!(!is_valid_name("1abc"));
assert!(!is_valid_name("foo bar"));
assert!(!is_valid_name(&"a".repeat(65)));
}
#[test]
fn validate_surface_error() {
let e = validate_name("").unwrap_err();
assert!(matches!(e, crate::error::CargoError::InvalidName(_)));
}
#[test]
fn index_path_layout_by_length() {
assert_eq!(index_path("a"), "1/a");
assert_eq!(index_path("ab"), "2/ab");
assert_eq!(index_path("abc"), "3/a/abc");
assert_eq!(index_path("serde"), "se/rd/serde");
assert_eq!(index_path("tokio"), "to/ki/tokio");
assert_eq!(index_path("abcd"), "ab/cd/abcd");
}
#[test]
fn index_path_lowercases() {
assert_eq!(index_path("Serde"), "se/rd/serde");
assert_eq!(index_path("AB"), "2/ab");
}
}