use crate::error::{Error, Result};
pub const MAX_COLLECTION_NAME_LENGTH: usize = 128;
pub const MIN_DIMENSION: usize = 1;
pub const MAX_DIMENSION: usize = 65_536;
pub fn validate_dimension(dimension: usize) -> Result<()> {
if !(MIN_DIMENSION..=MAX_DIMENSION).contains(&dimension) {
return Err(Error::InvalidDimension {
dimension,
min: MIN_DIMENSION,
max: MAX_DIMENSION,
});
}
Ok(())
}
pub fn validate_dimension_match(expected: usize, actual: usize) -> Result<()> {
if actual != expected {
return Err(Error::DimensionMismatch { expected, actual });
}
Ok(())
}
pub fn validate_vector_is_finite(vector: &[f32]) -> Result<()> {
for (index, &value) in vector.iter().enumerate() {
if !value.is_finite() {
return Err(Error::InvalidVector(format!(
"vector component at index {index} is not finite: {value}"
)));
}
}
Ok(())
}
pub fn validate_collection_name(name: &str) -> Result<()> {
if name.is_empty() {
return Err(invalid_name(name, "must not be empty"));
}
if name.len() > MAX_COLLECTION_NAME_LENGTH {
return Err(invalid_name(
name,
&format!("exceeds maximum length of {MAX_COLLECTION_NAME_LENGTH} characters"),
));
}
if name == "." || name == ".." {
return Err(invalid_name(name, "path traversal is not allowed"));
}
if name.starts_with('-') {
return Err(invalid_name(name, "must not start with a hyphen"));
}
if let Some(bad) = name.chars().find(|c| !is_valid_name_char(*c)) {
return Err(invalid_name(
name,
&format!(
"contains forbidden character '{bad}'; \
only ASCII letters, digits, underscores, and hyphens are allowed"
),
));
}
if is_windows_reserved(name) {
return Err(invalid_name(name, "is a Windows reserved device name"));
}
Ok(())
}
fn is_valid_name_char(c: char) -> bool {
c.is_ascii_alphanumeric() || c == '_' || c == '-'
}
fn is_windows_reserved(name: &str) -> bool {
let upper = name.to_ascii_uppercase();
matches!(
upper.as_str(),
"CON"
| "PRN"
| "AUX"
| "NUL"
| "COM1"
| "COM2"
| "COM3"
| "COM4"
| "COM5"
| "COM6"
| "COM7"
| "COM8"
| "COM9"
| "LPT1"
| "LPT2"
| "LPT3"
| "LPT4"
| "LPT5"
| "LPT6"
| "LPT7"
| "LPT8"
| "LPT9"
)
}
fn invalid_name(name: &str, reason: &str) -> Error {
Error::InvalidCollectionName {
name: name.to_string(),
reason: reason.to_string(),
}
}
#[cfg(test)]
#[path = "validation_tests.rs"]
mod tests;