use crate::error::{Result, YantrikDbError};
pub fn validate_embedding(
path: &'static str,
embedding: &[f32],
expected_dim: usize,
) -> Result<()> {
if embedding.len() != expected_dim {
return Err(YantrikDbError::InvalidEmbedding {
path,
index: None,
reason: format!(
"dimension mismatch: expected {expected_dim}, got {}",
embedding.len()
),
});
}
if let Some(index) = embedding.iter().position(|v| !v.is_finite()) {
return Err(YantrikDbError::InvalidEmbedding {
path,
index: Some(index),
reason: format!("non-finite element {} at index {index}", embedding[index]),
});
}
Ok(())
}
pub fn validate_scalars(path: &'static str, fields: &[(&'static str, f64)]) -> Result<()> {
for &(field, value) in fields {
if !value.is_finite() {
return Err(YantrikDbError::InvalidScalar { path, field, value });
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn accepts_valid_embedding_and_scalars() {
assert!(validate_embedding("record", &[0.1, -0.2, 0.3], 3).is_ok());
assert!(validate_scalars("record", &[("importance", 0.5), ("valence", -1.0)]).is_ok());
}
#[test]
fn rejects_dimension_mismatch() {
let err = validate_embedding("record", &[0.1, 0.2], 3).unwrap_err();
match err {
YantrikDbError::InvalidEmbedding {
path,
index,
reason,
} => {
assert_eq!(path, "record");
assert_eq!(index, None);
assert!(reason.contains("expected 3, got 2"), "{reason}");
}
other => panic!("wrong error: {other}"),
}
}
#[test]
fn rejects_non_finite_elements_with_index() {
for bad in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY] {
let err = validate_embedding("insert_vector", &[0.1, bad, 0.3], 3).unwrap_err();
match err {
YantrikDbError::InvalidEmbedding { index, .. } => assert_eq!(index, Some(1)),
other => panic!("wrong error: {other}"),
}
}
}
#[test]
fn rejects_non_finite_scalars_by_field() {
let err = validate_scalars("record", &[("importance", 0.5), ("half_life", f64::NAN)])
.unwrap_err();
match err {
YantrikDbError::InvalidScalar { field, .. } => assert_eq!(field, "half_life"),
other => panic!("wrong error: {other}"),
}
}
}