use std::borrow::Cow;
use serde::Serialize;
use validator::{Validate, ValidationError, ValidationErrors, ValidationErrorsKind};
#[cfg(debug_assertions)]
pub const MAX_MULTIVECTOR_FLATTENED_LEN: usize = 32 * 1024;
#[cfg(not(debug_assertions))]
pub const MAX_MULTIVECTOR_FLATTENED_LEN: usize = 1024 * 1024;
pub fn validate_iter<T: Validate>(iter: impl Iterator<Item = T>) -> Result<(), ValidationErrors> {
let mut child_errors: Vec<ValidationErrors> = iter.filter_map(|v| v.validate().err()).collect();
if child_errors.is_empty() {
return Ok(());
}
let kind = if child_errors.len() == 1 {
ValidationErrorsKind::Struct(Box::new(child_errors.pop().unwrap()))
} else {
ValidationErrorsKind::List(
child_errors
.into_iter()
.enumerate()
.map(|(i, e)| (i, Box::new(e)))
.collect(),
)
};
let mut bag = ValidationErrors::new();
bag.errors_mut().insert(Cow::Borrowed("?"), kind);
Err(bag)
}
#[inline]
pub fn validate_range_generic<N>(
value: N,
min: Option<N>,
max: Option<N>,
) -> Result<(), ValidationError>
where
N: PartialOrd + Serialize,
{
if min.as_ref().is_none_or(|min| &value >= min) && max.as_ref().is_none_or(|max| &value <= max)
{
return Ok(());
}
let mut err = ValidationError::new("range");
if let Some(min) = min {
err.add_param(Cow::from("min"), &min);
}
if let Some(max) = max {
err.add_param(Cow::from("max"), &max);
}
Err(err)
}
pub fn sparse_turbo4_unsupported_error() -> ValidationError {
let mut err = ValidationError::new("unsupported_sparse_datatype");
err.message = Some(Cow::Borrowed(
"sparse vectors do not support the `turbo4` datatype",
));
err
}
pub fn validate_not_empty(value: &str) -> Result<(), ValidationError> {
if value.is_empty() {
Err(ValidationError::new("not_empty"))
} else {
Ok(())
}
}
const INVALID_NAME_CHARS: [char; 11] =
['<', '>', ':', '"', '/', '\\', '|', '?', '*', '\0', '\u{1F}'];
fn check_invalid_name_chars(value: &str, kind: &str) -> Result<(), ValidationError> {
let Some(c) = INVALID_NAME_CHARS.into_iter().find(|c| value.contains(*c)) else {
return Ok(());
};
let mut err = ValidationError::new("does_not_contain");
err.add_param(Cow::from("pattern"), &c);
err.message
.replace(format!("{kind} cannot contain \"{c}\" char").into());
Err(err)
}
pub fn validate_collection_name(value: &str) -> Result<(), ValidationError> {
check_invalid_name_chars(value, "collection name")
}
pub fn validate_vector_name(value: &str) -> Result<(), ValidationError> {
const MAX_LEN: usize = 200;
if value.len() > MAX_LEN {
let mut err = ValidationError::new("length");
err.add_param(Cow::from("max"), &MAX_LEN);
err.add_param(Cow::from("actual"), &value.len());
err.message
.replace(format!("vector name must be at most {MAX_LEN} bytes long").into());
return Err(err);
}
check_invalid_name_chars(value, "vector name")
}
pub fn validate_collection_name_legacy(value: &str) -> Result<(), ValidationError> {
const INVALID_CHARS: [char; 2] = ['/', '\0'];
match INVALID_CHARS.into_iter().find(|c| value.contains(*c)) {
Some(c) => {
let mut err = ValidationError::new("does_not_contain");
err.add_param(Cow::from("pattern"), &c);
err.message
.replace(format!("collection name cannot contain \"{c}\" char").into());
Err(err)
}
None => Ok(()),
}
}
pub fn validate_geo_polygon<T>(points: &[T]) -> Result<(), ValidationError>
where
T: PartialEq,
{
let min_length = 4;
if points.len() < min_length {
let mut err = ValidationError::new("min_polygon_length");
err.add_param(Cow::from("length"), &points.len());
err.add_param(Cow::from("min_length"), &min_length);
return Err(err);
}
let first_point = &points[0];
let last_point = &points[points.len() - 1];
if first_point != last_point {
return Err(ValidationError::new("closed_polygon"));
}
Ok(())
}
pub fn validate_shard_different_peers(
from_peer_id: u64,
to_peer_id: u64,
shard_id: u32,
to_shard_id: Option<u32>,
) -> Result<(), ValidationErrors> {
if to_peer_id != from_peer_id {
return Ok(());
}
if to_shard_id.is_some_and(|to_shard_id| to_shard_id != shard_id) {
return Ok(());
}
let mut errors = ValidationErrors::new();
errors.add("to_peer_id", {
let mut error = ValidationError::new("must_not_match");
error.add_param(Cow::from("value"), &to_peer_id.to_string());
error.add_param(Cow::from("other_field"), &"from_peer_id");
error.add_param(Cow::from("other_value"), &from_peer_id.to_string());
error.add_param(
Cow::from("message"),
&format!("cannot transfer shard to itself, \"to_peer_id\" must be different than {from_peer_id} in \"from_peer_id\""),
);
error
});
Err(errors)
}
pub fn validate_sha256_hash(value: &str) -> Result<(), ValidationError> {
if value.len() != 64 {
let mut err = ValidationError::new("invalid_sha256_hash");
err.add_param(Cow::from("length"), &value.len());
err.add_param(Cow::from("expected_length"), &64);
return Err(err);
}
if !value.chars().all(|c| c.is_ascii_hexdigit()) {
let mut err = ValidationError::new("invalid_sha256_hash");
err.add_param(
Cow::from("message"),
&"invalid characters, expected 0-9, a-f, A-F",
);
return Err(err);
}
Ok(())
}
pub fn validate_multi_vector_by_length(multivec_length: &[usize]) -> Result<(), ValidationErrors> {
if multivec_length.is_empty() {
let mut errors = ValidationErrors::default();
let mut err = ValidationError::new("empty_multi_vector");
err.add_param(Cow::from("message"), &"multi vector must not be empty");
errors.add("data", err);
return Err(errors);
}
if multivec_length.contains(&0) {
let mut errors = ValidationErrors::default();
let mut err = ValidationError::new("empty_vector");
err.add_param(Cow::from("message"), &"all vectors must be non-empty");
errors.add("data", err);
return Err(errors);
}
let flattened_len = multivec_length.iter().sum::<usize>();
if flattened_len >= MAX_MULTIVECTOR_FLATTENED_LEN {
let mut errors = ValidationErrors::default();
let mut err = ValidationError::new("multi_vector_too_large");
err.add_param(Cow::from("message"), &format!("Total size of all vectors ({flattened_len}) must be less than {MAX_MULTIVECTOR_FLATTENED_LEN}"));
errors.add("data", err);
return Err(errors);
}
let dim = multivec_length[0];
if let Some(bad_vec) = multivec_length.iter().find(|v| **v != dim) {
let mut errors = ValidationErrors::default();
let mut err = ValidationError::new("inconsistent_multi_vector");
err.add_param(
Cow::from("message"),
&format!(
"all vectors must have the same dimension, found vector with dimension {bad_vec}",
),
);
errors.add("data", err);
return Err(errors);
}
Ok(())
}
pub fn validate_multi_vector<T>(multivec: &[Vec<T>]) -> Result<(), ValidationErrors> {
let multivec_length: Vec<_> = multivec.iter().map(|v| v.len()).collect();
validate_multi_vector_by_length(&multivec_length)
}
pub fn validate_multi_vector_len(
vectors_count: u32,
flatten_dense_vector: &[f32],
) -> Result<(), ValidationErrors> {
if vectors_count == 0 {
let mut errors = ValidationErrors::default();
let mut err = ValidationError::new("invalid_vector_count");
err.add_param(
Cow::from("vectors_count"),
&"vectors count must be greater than 0",
);
errors.add("data", err);
return Err(errors);
}
if flatten_dense_vector.is_empty() {
let mut errors = ValidationErrors::default();
let mut err = ValidationError::new("empty_multi_vector");
err.add_param(Cow::from("message"), &"multi vector must not be empty");
errors.add("data", err);
return Err(errors);
}
let dense_vector_len = flatten_dense_vector.len();
if dense_vector_len >= MAX_MULTIVECTOR_FLATTENED_LEN {
let mut errors = ValidationErrors::default();
let mut err = ValidationError::new("Vector size is too large");
err.add_param(Cow::from("vector_len"), &dense_vector_len);
err.add_param(Cow::from("vectors_count"), &vectors_count);
errors.add("data", err);
return Err(errors);
}
if !dense_vector_len.is_multiple_of(vectors_count as usize) {
let mut errors = ValidationErrors::default();
let mut err = ValidationError::new("invalid dense vector length for vectors count");
err.add_param(Cow::from("vector_len"), &dense_vector_len);
err.add_param(Cow::from("vectors_count"), &vectors_count);
errors.add("data", err);
Err(errors)
} else {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_multi_vector_len_rejects_empty_data() {
assert!(validate_multi_vector_len(2, &[]).is_err());
assert!(validate_multi_vector_len(2, &[1.0, 2.0, 3.0, 4.0]).is_ok());
}
#[test]
fn test_validate_range_generic() {
assert!(validate_range_generic(u64::MIN, None, None).is_ok());
assert!(validate_range_generic(u64::MAX, None, None).is_ok());
assert!(validate_range_generic(1, Some(1), None).is_ok());
assert!(validate_range_generic(0, Some(1), None).is_err());
assert!(validate_range_generic(1.0, Some(1.0), None).is_ok());
assert!(validate_range_generic(0.0, Some(1.0), None).is_err());
assert!(validate_range_generic(1, None, Some(1)).is_ok());
assert!(validate_range_generic(2, None, Some(1)).is_err());
assert!(validate_range_generic(1.0, None, Some(1.0)).is_ok());
assert!(validate_range_generic(2.0, None, Some(1.0)).is_err());
assert!(validate_range_generic(0, Some(1), Some(1)).is_err());
assert!(validate_range_generic(1, Some(1), Some(1)).is_ok());
assert!(validate_range_generic(2, Some(1), Some(1)).is_err());
assert!(validate_range_generic(0, Some(1), Some(2)).is_err());
assert!(validate_range_generic(1, Some(1), Some(2)).is_ok());
assert!(validate_range_generic(2, Some(1), Some(2)).is_ok());
assert!(validate_range_generic(3, Some(1), Some(2)).is_err());
assert!(validate_range_generic(0, Some(2), Some(1)).is_err());
assert!(validate_range_generic(1, Some(2), Some(1)).is_err());
assert!(validate_range_generic(2, Some(2), Some(1)).is_err());
assert!(validate_range_generic(3, Some(2), Some(1)).is_err());
assert!(validate_range_generic(0.0, Some(1.0), Some(1.0)).is_err());
assert!(validate_range_generic(1.0, Some(1.0), Some(1.0)).is_ok());
assert!(validate_range_generic(2.0, Some(1.0), Some(1.0)).is_err());
assert!(validate_range_generic(0.0, Some(1.0), Some(2.0)).is_err());
assert!(validate_range_generic(1.0, Some(1.0), Some(2.0)).is_ok());
assert!(validate_range_generic(2.0, Some(1.0), Some(2.0)).is_ok());
assert!(validate_range_generic(3.0, Some(1.0), Some(2.0)).is_err());
assert!(validate_range_generic(0.0, Some(2.0), Some(1.0)).is_err());
assert!(validate_range_generic(1.0, Some(2.0), Some(1.0)).is_err());
assert!(validate_range_generic(2.0, Some(2.0), Some(1.0)).is_err());
assert!(validate_range_generic(3.0, Some(2.0), Some(1.0)).is_err());
}
#[test]
fn test_validate_not_empty() {
assert!(validate_not_empty("not empty").is_ok());
assert!(validate_not_empty(" ").is_ok());
assert!(validate_not_empty("").is_err());
}
#[test]
fn test_validate_collection_name() {
assert!(validate_collection_name("test_collection").is_ok());
assert!(validate_collection_name("").is_ok());
assert!(validate_collection_name("no/path").is_err());
assert!(validate_collection_name("no*path").is_err());
assert!(validate_collection_name("?").is_err());
assert!(validate_collection_name("\0").is_err());
assert!(validate_collection_name_legacy("test_collection").is_ok());
assert!(validate_collection_name_legacy("").is_ok());
assert!(validate_collection_name_legacy("no/path").is_err());
assert!(validate_collection_name_legacy("no*path").is_ok());
assert!(validate_collection_name_legacy("?").is_ok());
assert!(validate_collection_name_legacy("\0").is_err());
}
#[test]
fn test_validate_geo_polygon() {
let bad_polygon: Vec<(f64, f64)> = vec![];
assert!(
validate_geo_polygon(&bad_polygon).is_err(),
"bad polygon should error on validation",
);
let bad_polygon = vec![(1., 1.), (2., 2.), (3., 3.)];
assert!(
validate_geo_polygon(&bad_polygon).is_err(),
"bad polygon should error on validation",
);
let bad_polygon = vec![(1., 1.), (2., 2.), (3., 3.), (4., 4.)];
assert!(
validate_geo_polygon(&bad_polygon).is_err(),
"bad polygon should error on validation"
);
let good_polygon = vec![(1., 1.), (2., 2.), (3., 3.), (1., 1.)];
assert!(
validate_geo_polygon(&good_polygon).is_ok(),
"good polygon should not error on validation",
);
}
#[test]
fn test_validate_iter() {
#[derive(validator::Validate)]
struct Item {
#[validate(range(min = 1))]
idx: u32,
}
assert!(validate_iter(std::iter::empty::<&Item>()).is_ok());
let valid = [Item { idx: 1 }, Item { idx: 2 }];
assert!(validate_iter(valid.iter()).is_ok());
let one_bad = [Item { idx: 0 }];
let err = validate_iter(one_bad.iter()).expect_err("should fail");
match err.errors().get("?") {
Some(ValidationErrorsKind::Struct(_)) => {}
other => panic!("expected Struct under `?`, got {other:?}"),
}
let many_bad = [Item { idx: 0 }, Item { idx: 0 }, Item { idx: 0 }];
let err = validate_iter(many_bad.iter()).expect_err("should fail");
match err.errors().get("?") {
Some(ValidationErrorsKind::List(list)) => assert_eq!(list.len(), 3),
other => panic!("expected List under `?`, got {other:?}"),
}
}
#[test]
fn test_validate_sha256_hash() {
assert!(
validate_sha256_hash(
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
)
.is_ok(),
);
assert!(
validate_sha256_hash("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde")
.is_err(),
);
assert!(
validate_sha256_hash(
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0"
)
.is_err(),
);
assert!(
validate_sha256_hash(
"0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEG"
)
.is_err(),
);
}
}