use super::NewError;
pub fn validate_name(name: &str) -> Result<(), NewError> {
let crate_error = crate_name_error(name);
let gleam_error = gleam_name_error(name);
match (crate_error, gleam_error) {
(None, None) => Ok(()),
(Some(crate_reason), None) => Err(NewError::InvalidCrateName {
name: name.to_owned(),
reason: crate_reason,
}),
(None, Some(gleam_reason)) => Err(NewError::InvalidGleamName {
name: name.to_owned(),
reason: gleam_reason,
}),
(Some(crate_reason), Some(gleam_reason)) => Err(NewError::InvalidBothNames {
name: name.to_owned(),
crate_reason,
gleam_reason,
}),
}
}
fn crate_name_error(name: &str) -> Option<&'static str> {
const KEYWORDS: &[&str] = &[
"Self", "abstract", "as", "async", "await", "become", "box", "break", "const", "continue",
"crate", "do", "dyn", "else", "enum", "extern", "false", "final", "fn", "for", "gen", "if",
"impl", "in", "let", "loop", "macro", "match", "mod", "move", "mut", "override", "priv",
"pub", "ref", "return", "self", "static", "struct", "super", "trait", "true", "try",
"type", "typeof", "union", "unsafe", "unsized", "use", "virtual", "where", "while",
"yield",
];
let mut chars = name.chars();
if name == "_"
|| !chars
.next()
.is_some_and(|first| first == '_' || first.is_ascii_alphabetic())
|| !chars.all(|character| character == '_' || character.is_ascii_alphanumeric())
{
return Some("it must be an ASCII Rust identifier");
}
KEYWORDS
.contains(&name)
.then_some("Rust keywords are not crate identifiers")
}
fn gleam_name_error(name: &str) -> Option<&'static str> {
let mut chars = name.chars();
if !chars.next().is_some_and(|first| first.is_ascii_lowercase())
|| !chars.all(|character| {
character == '_' || character.is_ascii_lowercase() || character.is_ascii_digit()
})
{
Some(
"it must start with a lowercase ASCII letter and contain only lowercase letters, digits, or underscores",
)
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::validate_name;
#[test]
fn invalid_names_report_crate_gleam_and_both_rules() {
let crate_error = validate_name("type").err().map(|error| error.to_string());
assert!(crate_error.is_some_and(|message| {
message.contains("Rust crate identifier") && message.contains("[a-z][a-z0-9_]*")
}));
let gleam_error = validate_name("Hello").err().map(|error| error.to_string());
assert!(gleam_error.is_some_and(|message| {
message.contains("Gleam project name") && message.contains("[a-z][a-z0-9_]*")
}));
let both_error = validate_name("bad-name")
.err()
.map(|error| error.to_string());
assert!(both_error.is_some_and(|message| {
message.contains("fails both rules")
&& message.contains("Rust crate identifier")
&& message.contains("Gleam project name")
}));
}
}