use crate::error::{OxCacheError, OxCacheResult};
#[derive(Debug, Clone, Copy)]
pub struct ConfigValidation;
impl ConfigValidation {
#[allow(dead_code)] pub const MAX_CAPACITY: u64 = 1_000_000_000;
#[allow(dead_code)] pub const MAX_TTL_SECS: u64 = 30 * 24 * 60 * 60;
#[allow(dead_code)] pub const MAX_TTI_SECS: u64 = 30 * 24 * 60 * 60;
pub const MAX_CUSTOM_NAME_LENGTH: usize = 256;
pub const VALID_NAME_CHARS: &'static str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.-";
#[cfg(feature = "redis")]
pub fn detect_valkey(conn: &mut redis::Connection) -> OxCacheResult<bool> {
let info: String = redis::cmd("INFO")
.arg("server")
.query(conn)
.map_err(|e| OxCacheError::Connection(format!("Failed to query INFO server: {}", e)))?;
Ok(info.contains("valkey"))
}
#[inline]
fn is_valid_name_char(c: char) -> bool {
c.is_ascii_alphanumeric() || matches!(c, '_' | '-' | '.')
}
pub fn validate_custom_name(name: &str) -> OxCacheResult<String> {
if name.is_empty() {
return Err(OxCacheError::InvalidInput(
"Custom backend name cannot be empty".to_string(),
));
}
for (i, ch) in name.char_indices() {
if i >= Self::MAX_CUSTOM_NAME_LENGTH {
return Err(OxCacheError::InvalidInput(format!(
"Custom backend name exceeds maximum length of {} characters",
Self::MAX_CUSTOM_NAME_LENGTH
)));
}
if !Self::is_valid_name_char(ch) {
return Err(OxCacheError::InvalidInput(format!(
"Custom backend name contains invalid character '{}'. Allowed characters: {}",
ch,
Self::VALID_NAME_CHARS
)));
}
}
Ok(name.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_custom_name_valid() {
assert_eq!(
ConfigValidation::validate_custom_name("my_backend").unwrap(),
"my_backend"
);
assert_eq!(
ConfigValidation::validate_custom_name("backend-1").unwrap(),
"backend-1"
);
assert_eq!(ConfigValidation::validate_custom_name("app.v2").unwrap(), "app.v2");
assert_eq!(ConfigValidation::validate_custom_name("ABC123").unwrap(), "ABC123");
assert_eq!(ConfigValidation::validate_custom_name("a").unwrap(), "a");
}
#[test]
fn test_validate_custom_name_empty() {
let result = ConfigValidation::validate_custom_name("");
assert!(result.is_err());
match result.unwrap_err() {
OxCacheError::InvalidInput(msg) => assert!(msg.contains("cannot be empty")),
_ => panic!("Expected InvalidInput error"),
}
}
#[test]
fn test_validate_custom_name_too_long() {
let long_name = "a".repeat(ConfigValidation::MAX_CUSTOM_NAME_LENGTH + 1);
let result = ConfigValidation::validate_custom_name(&long_name);
assert!(result.is_err());
match result.unwrap_err() {
OxCacheError::InvalidInput(msg) => assert!(msg.contains("maximum length")),
_ => panic!("Expected InvalidInput error"),
}
}
#[test]
fn test_validate_custom_name_exactly_max_length() {
let name = "a".repeat(ConfigValidation::MAX_CUSTOM_NAME_LENGTH);
assert!(ConfigValidation::validate_custom_name(&name).is_ok());
}
#[test]
fn test_validate_custom_name_invalid_chars() {
assert!(ConfigValidation::validate_custom_name("name with space").is_err());
assert!(ConfigValidation::validate_custom_name("name:colon").is_err());
assert!(ConfigValidation::validate_custom_name("name/slash").is_err());
assert!(ConfigValidation::validate_custom_name("name@at").is_err());
assert!(ConfigValidation::validate_custom_name("name#hash").is_err());
assert!(ConfigValidation::validate_custom_name("name$dollar").is_err());
}
#[test]
fn test_validate_custom_name_all_valid_chars() {
let name: String = ConfigValidation::VALID_NAME_CHARS.chars().collect();
assert!(ConfigValidation::validate_custom_name(&name).is_ok());
}
#[test]
fn test_config_validation_constants() {
assert_eq!(ConfigValidation::MAX_CUSTOM_NAME_LENGTH, 256);
assert!(ConfigValidation::VALID_NAME_CHARS.contains('a'));
assert!(ConfigValidation::VALID_NAME_CHARS.contains('Z'));
assert!(ConfigValidation::VALID_NAME_CHARS.contains('0'));
assert!(ConfigValidation::VALID_NAME_CHARS.contains('_'));
assert!(ConfigValidation::VALID_NAME_CHARS.contains('-'));
assert!(ConfigValidation::VALID_NAME_CHARS.contains('.'));
assert!(!ConfigValidation::VALID_NAME_CHARS.contains(':'));
assert!(!ConfigValidation::VALID_NAME_CHARS.contains('/'));
assert!(!ConfigValidation::VALID_NAME_CHARS.contains(' '));
}
#[test]
fn test_validate_custom_name_multibyte_unicode() {
let name_with_dots = "a.b.c";
assert_eq!(name_with_dots.chars().count(), 5);
assert_eq!(name_with_dots.len(), 5);
assert!(ConfigValidation::validate_custom_name(name_with_dots).is_ok());
let max_name = "a".repeat(256);
assert!(ConfigValidation::validate_custom_name(&max_name).is_ok());
let over_name = "a".repeat(257);
assert!(ConfigValidation::validate_custom_name(&over_name).is_err());
}
}