fast-fs 0.2.1

High-speed async file system traversal library with batteries-included file browser component
Documentation
// <FILE>crates/fast-fs/src/nav/fnc_validate.rs</FILE> - <DESC>Filename validation helper</DESC>
// <VERS>VERSION: 0.1.0</VERS>
// <WCTX>Implementing nav module PRD</WCTX>
// <CLOG>Initial creation</CLOG>

//! Filename validation
//!
//! Provides validation for filenames used in create/rename operations.
//! Use this for live validation in your UI (e.g., disable Create button).

use super::nav_error::NavError;

/// Validate a filename for create/rename operations.
///
/// Call this for live validation in your UI (e.g., disable Create button
/// when the name is invalid).
///
/// # Arguments
///
/// * `name` - The filename to validate (not a full path)
///
/// # Returns
///
/// * `Ok(())` if the name is valid
/// * `Err(NavError::InvalidName)` if the name is invalid
///
/// # Examples
///
/// ```
/// use fast_fs::nav::validate_name;
///
/// assert!(validate_name("document.txt").is_ok());
/// assert!(validate_name("").is_err());
/// assert!(validate_name("..").is_err());
/// assert!(validate_name("foo/bar").is_err());
/// ```
pub fn validate_name(name: &str) -> Result<(), NavError> {
    // Empty name
    if name.is_empty() {
        return Err(NavError::InvalidName("name cannot be empty".to_string()));
    }

    // Contains path separators
    if name.contains('/') || name.contains('\\') {
        return Err(NavError::InvalidName(
            "name cannot contain path separators".to_string(),
        ));
    }

    // Is . or ..
    if name == "." || name == ".." {
        return Err(NavError::InvalidName("invalid name".to_string()));
    }

    // Windows reserved characters (also problematic on many filesystems)
    #[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(),
            ));
        }

        // Windows reserved names
        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(),
            ));
        }
    }

    // Control characters
    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")
        ));
    }
}

// <FILE>crates/fast-fs/src/nav/fnc_validate.rs</FILE>
// <VERS>END OF VERSION: 0.1.0</VERS>