use std::error::Error;
use std::fmt::{self, Display, Formatter};
use serde::{Deserialize, Serialize};
use lodestone_types::{
DenseVectorDefinition, DenseVectorElementType, MAX_VECTOR_DIMENSIONS, VectorMetric, VectorQuantizationScale,
};
pub const NATIVE_VECTOR_MAPPING_FORMAT_VERSION: u16 = 2;
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum NativeVectorMetricV2 {
Cosine,
DotProduct,
SquaredL2,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct NativeVectorMappingV2 {
pub format_version: u16,
#[serde(rename = "type")]
pub mapping_type: NativeVectorMappingTypeV2,
pub dimensions: u16,
pub metric: NativeVectorMetricV2,
pub element_type: DenseVectorElementType,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub quantization_scale: Option<VectorQuantizationScale>,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum NativeVectorMappingTypeV2 {
Vector,
}
impl NativeVectorMappingV2 {
pub fn validate(&self) -> Result<(), NativeVectorMappingError> {
if self.format_version != NATIVE_VECTOR_MAPPING_FORMAT_VERSION {
return Err(NativeVectorMappingError::UnsupportedVersion(self.format_version));
}
if self.dimensions == 0 || self.dimensions > MAX_VECTOR_DIMENSIONS {
return Err(NativeVectorMappingError::InvalidDimensions(self.dimensions));
}
DenseVectorDefinition {
dimensions: self.dimensions,
element_type: self.element_type,
metric: match self.metric {
NativeVectorMetricV2::Cosine => VectorMetric::Cosine,
NativeVectorMetricV2::DotProduct => VectorMetric::DotProduct,
NativeVectorMetricV2::SquaredL2 => VectorMetric::SquaredL2,
},
quantization_scale: self.quantization_scale,
}
.validate()
.map_err(|_| NativeVectorMappingError::InvalidElementContract)?;
Ok(())
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum NativeVectorMappingError {
UnsupportedVersion(u16),
InvalidDimensions(u16),
InvalidElementContract,
}
impl Display for NativeVectorMappingError {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::UnsupportedVersion(version) => {
write!(formatter, "unsupported native vector mapping version {version}")
}
Self::InvalidDimensions(dimensions) => write!(
formatter,
"native vector mapping dimensions {dimensions} are outside 1..={MAX_VECTOR_DIMENSIONS}"
),
Self::InvalidElementContract => formatter.write_str("native vector mapping element contract is invalid"),
}
}
}
impl Error for NativeVectorMappingError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mapping_refuses_unknown_version_and_zero_dimensions() {
let mut mapping = NativeVectorMappingV2 {
format_version: NATIVE_VECTOR_MAPPING_FORMAT_VERSION,
mapping_type: NativeVectorMappingTypeV2::Vector,
dimensions: 2,
metric: NativeVectorMetricV2::Cosine,
element_type: DenseVectorElementType::F32,
quantization_scale: None,
};
assert_eq!(mapping.validate(), Ok(()));
mapping.format_version += 1;
assert!(matches!(
mapping.validate(),
Err(NativeVectorMappingError::UnsupportedVersion(_))
));
mapping.format_version = NATIVE_VECTOR_MAPPING_FORMAT_VERSION;
mapping.dimensions = 0;
assert_eq!(mapping.validate(), Err(NativeVectorMappingError::InvalidDimensions(0)));
}
}