use super::nav_error::NavError;
pub fn validate_name(name: &str) -> Result<(), NavError> {
if name.is_empty() {
return Err(NavError::InvalidName("name cannot be empty".to_string()));
}
if name.contains('/') || name.contains('\\') {
return Err(NavError::InvalidName(
"name cannot contain path separators".to_string(),
));
}
if name == "." || name == ".." {
return Err(NavError::InvalidName("invalid name".to_string()));
}
#[cfg(windows)]
{
const RESERVED_CHARS: &[char] = &['<', '>', ':', '"', '|', '?', '*'];
if name.chars().any(|c| RESERVED_CHARS.contains(&c)) {
return Err(NavError::InvalidName(
"name contains reserved characters".to_string(),
));
}
const RESERVED_NAMES: &[&str] = &[
"CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7",
"COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
];
let upper = name.to_uppercase();
let base = upper.split('.').next().unwrap_or("");
if RESERVED_NAMES.contains(&base) {
return Err(NavError::InvalidName(
"name is a reserved Windows name".to_string(),
));
}
}
if name.chars().any(|c| c.is_control()) {
return Err(NavError::InvalidName(
"name cannot contain control characters".to_string(),
));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_valid_names() {
assert!(validate_name("document.txt").is_ok());
assert!(validate_name("my-file").is_ok());
assert!(validate_name("file_name.rs").is_ok());
assert!(validate_name(".hidden").is_ok());
assert!(validate_name("file.tar.gz").is_ok());
assert!(validate_name("日本語.txt").is_ok());
}
#[test]
fn test_empty() {
assert!(matches!(
validate_name(""),
Err(NavError::InvalidName(msg)) if msg.contains("empty")
));
}
#[test]
fn test_path_separators() {
assert!(matches!(
validate_name("foo/bar"),
Err(NavError::InvalidName(msg)) if msg.contains("separator")
));
assert!(matches!(
validate_name("foo\\bar"),
Err(NavError::InvalidName(msg)) if msg.contains("separator")
));
}
#[test]
fn test_dot_names() {
assert!(matches!(validate_name("."), Err(NavError::InvalidName(_))));
assert!(matches!(validate_name(".."), Err(NavError::InvalidName(_))));
}
#[test]
fn test_control_characters() {
assert!(matches!(
validate_name("file\x00name"),
Err(NavError::InvalidName(msg)) if msg.contains("control")
));
assert!(matches!(
validate_name("file\nname"),
Err(NavError::InvalidName(msg)) if msg.contains("control")
));
}
}