use crate::error::ExtensionError;
const MAX_FUNCTION_NAME_LEN: usize = 256;
pub fn validate_function_name(name: &str) -> Result<(), ExtensionError> {
if name.is_empty() {
return Err(ExtensionError::new("function name must not be empty"));
}
if name.len() > MAX_FUNCTION_NAME_LEN {
return Err(ExtensionError::new(format!(
"function name must not exceed {MAX_FUNCTION_NAME_LEN} characters, got {}",
name.len()
)));
}
if name.bytes().any(|b| b == 0) {
return Err(ExtensionError::new(
"function name must not contain null bytes",
));
}
let first = name.as_bytes()[0];
if !first.is_ascii_lowercase() && first != b'_' {
return Err(ExtensionError::new(format!(
"function name must start with a lowercase letter or underscore, got '{}'",
name.chars().next().unwrap_or('?')
)));
}
for (i, ch) in name.chars().enumerate() {
if !matches!(ch, 'a'..='z' | '0'..='9' | '_') {
return Err(ExtensionError::new(format!(
"function name contains invalid character '{ch}' at position {i}; \
only lowercase letters, digits, and underscores are allowed"
)));
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn valid_simple() {
assert!(validate_function_name("word_count").is_ok());
}
#[test]
fn valid_with_digits() {
assert!(validate_function_name("my_func_v2").is_ok());
}
#[test]
fn valid_underscore_prefix() {
assert!(validate_function_name("_internal").is_ok());
}
#[test]
fn valid_single_char() {
assert!(validate_function_name("f").is_ok());
}
#[test]
fn empty_rejected() {
let err = validate_function_name("").unwrap_err();
assert!(err.as_str().contains("empty"));
}
#[test]
fn uppercase_rejected() {
let err = validate_function_name("MyFunc").unwrap_err();
assert!(err.as_str().contains("lowercase letter or underscore"));
}
#[test]
fn uppercase_mid_rejected() {
let err = validate_function_name("myFunc").unwrap_err();
assert!(err.as_str().contains("invalid character"));
}
#[test]
fn hyphen_rejected() {
let err = validate_function_name("my-func").unwrap_err();
assert!(err.as_str().contains("invalid character"));
}
#[test]
fn starts_with_digit_rejected() {
let err = validate_function_name("1func").unwrap_err();
assert!(err.as_str().contains("lowercase letter or underscore"));
}
#[test]
fn space_rejected() {
let err = validate_function_name("my func").unwrap_err();
assert!(err.as_str().contains("invalid character"));
}
#[test]
fn special_char_rejected() {
let err = validate_function_name("my@func").unwrap_err();
assert!(err.as_str().contains("invalid character"));
}
#[test]
fn null_byte_rejected() {
let err = validate_function_name("my\0func").unwrap_err();
assert!(err.as_str().contains("null bytes"));
}
#[test]
fn too_long_rejected() {
let long_name: String = "a".repeat(257);
let err = validate_function_name(&long_name).unwrap_err();
assert!(err.as_str().contains("256 characters"));
}
#[test]
fn max_length_accepted() {
let max_name: String = "a".repeat(256);
assert!(validate_function_name(&max_name).is_ok());
}
#[test]
fn semicolon_rejected() {
let err = validate_function_name("func;drop").unwrap_err();
assert!(err.as_str().contains("invalid character"));
}
#[test]
fn quote_rejected() {
let err = validate_function_name("func'name").unwrap_err();
assert!(err.as_str().contains("invalid character"));
}
}