mecha10-templates 0.8.2

Mecha10 project templates — code generation, init, and template services
Documentation
//! Shared identifier-name validation for generated behaviors/drivers/nodes.
//!
//! Names must be ASCII alphanumeric/hyphen/underscore and start with a letter.

use anyhow::Result;

/// Validate a user-supplied name for a generated behavior/driver/node.
/// `kind` is the capitalized noun used in error messages (e.g. `"Node"`).
pub fn validate_generator_name(kind: &str, name: &str) -> Result<()> {
    if name.is_empty() {
        anyhow::bail!("{} name cannot be empty", kind);
    }

    if !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_') {
        anyhow::bail!(
            "{} name '{}' contains invalid characters. Use only letters, numbers, hyphens, and underscores.",
            kind,
            name
        );
    }

    if !name.chars().next().map(|c| c.is_ascii_alphabetic()).unwrap_or(false) {
        anyhow::bail!("{} name must start with a letter", kind);
    }

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn rejects_empty() {
        let err = validate_generator_name("Node", "").unwrap_err();
        assert!(err.to_string().contains("cannot be empty"));
    }

    #[test]
    fn rejects_invalid_characters() {
        let err = validate_generator_name("Driver", "my driver!").unwrap_err();
        assert!(err.to_string().contains("invalid characters"));
    }

    #[test]
    fn rejects_leading_digit() {
        let err = validate_generator_name("Behavior", "1patrol").unwrap_err();
        assert!(err.to_string().contains("must start with a letter"));
    }

    #[test]
    fn accepts_kebab_and_snake_case() {
        assert!(validate_generator_name("Node", "my-motor_node").is_ok());
        assert!(validate_generator_name("Node", "Motor2").is_ok());
    }

    #[test]
    fn error_message_uses_supplied_kind() {
        let err = validate_generator_name("Behavior", "").unwrap_err();
        assert!(err.to_string().contains("Behavior name cannot be empty"));
    }
}