libvctrl_handler 5.2.0

Fundamental contracts for building a version control system – no implementations, only traits and types
Documentation
use std::path::Path;

use crate::constants::MAX_NAME_LENGTH;
use crate::errors::VctrlError;

pub fn validate_name(name: &str) -> Result<(), VctrlError> {
    if name.is_empty() {
        return Err(VctrlError::InvalidName("name is empty".into()));
    }
    let max_len = usize::try_from(MAX_NAME_LENGTH).unwrap_or(usize::MAX);
    if name.len() > max_len {
        return Err(VctrlError::InvalidName(format!(
            "name exceeds maximum length {MAX_NAME_LENGTH}: '{name}'"
        )));
    }
    if name.bytes().any(|b| b.is_ascii_control()) {
        return Err(VctrlError::InvalidName(format!(
            "name contains control characters: '{name}'"
        )));
    }
    Ok(())
}

pub fn validate_ref_name(name: &str) -> Result<(), VctrlError> {
    validate_name(name)?;

    if name == "@" {
        return Err(VctrlError::InvalidName("ref name cannot be '@'".into()));
    }

    if name.starts_with('/') || name.ends_with('/') || name.contains("//") {
        return Err(VctrlError::InvalidName(format!(
            "invalid ref name: '{name}'"
        )));
    }

    for component in name.split('/') {
        if component.is_empty()
            || component.starts_with('.')
            || Path::new(component)
                .extension()
                .is_some_and(|ext| ext.eq_ignore_ascii_case("lock"))
            || component.contains("..")
            || component.contains('~')
            || component.contains('^')
            || component.contains(':')
            || component.contains('?')
            || component.contains('*')
            || component.contains('[')
            || component.contains('\\')
            || component.contains(' ')
            || component.contains("@{")
            || component.contains('<')
            || component.contains('>')
            || component.contains('|')
            || component.contains('"')
        {
            return Err(VctrlError::InvalidName(format!(
                "invalid ref name: '{name}'"
            )));
        }
    }

    Ok(())
}

pub fn validate_tree_entry_name(name: &str) -> Result<(), VctrlError> {
    validate_name(name)?;
    if name.contains('/') || name.contains('\\') || name == "." || name == ".." {
        return Err(VctrlError::InvalidName(format!(
            "tree entry name contains forbidden path characters or names: '{name}'"
        )));
    }
    Ok(())
}