use crate::common::namespace::Namespace;
use crate::store::{
config::AdapterConfig,
error::{Error as StoreError, Result as StoreResult},
};
#[derive(Debug, Clone)]
pub struct VectorConfig {
pub default_namespace: Namespace,
pub dimension: Option<usize>,
pub table_name: String,
}
impl Default for VectorConfig {
fn default() -> Self {
Self {
default_namespace: Namespace::named("default"),
dimension: None,
table_name: "fornix_vectors".to_string(),
}
}
}
impl VectorConfig {
pub fn with_dimension(dim: usize) -> Self {
Self {
dimension: Some(dim),
..Default::default()
}
}
pub fn with_namespace(ns: impl Into<String>) -> Self {
Self {
default_namespace: Namespace::named(ns),
..Default::default()
}
}
pub fn resolve_namespace<'a>(&'a self, ns: Option<&'a Namespace>) -> &'a Namespace {
ns.unwrap_or(&self.default_namespace)
}
pub fn check_dimension(&self, actual: usize) -> StoreResult<()> {
if let Some(expected) = self.dimension {
if actual != expected {
return Err(StoreError::DimensionMismatch { expected, actual });
}
}
Ok(())
}
}
impl AdapterConfig for VectorConfig {
fn adapter_name(&self) -> &'static str {
"vector"
}
fn validate(&self) -> StoreResult<()> {
if self.default_namespace.is_default() {
return Err(StoreError::config(
"vector default_namespace must be a named namespace",
));
}
if let Some(dim) = self.dimension {
if dim == 0 {
return Err(StoreError::config("dimension must be greater than zero if set"));
}
}
if self.table_name.is_empty() {
return Err(StoreError::config("table_name must not be empty"));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::store::error::Error as StoreError;
#[test]
fn default_has_named_namespace() {
let c = VectorConfig::default();
assert_eq!(c.default_namespace.as_deref(), Some("default"));
}
#[test]
fn default_has_no_dimension() {
assert!(VectorConfig::default().dimension.is_none());
}
#[test]
fn default_table_name() {
assert_eq!(VectorConfig::default().table_name, "fornix_vectors");
}
#[test]
fn with_dimension_sets_dimension() {
let c = VectorConfig::with_dimension(384);
assert_eq!(c.dimension, Some(384));
}
#[test]
fn with_namespace_sets_namespace() {
let c = VectorConfig::with_namespace("docs");
assert_eq!(c.default_namespace.as_deref(), Some("docs"));
}
#[test]
fn adapter_name_is_vector() {
assert_eq!(VectorConfig::default().adapter_name(), "vector");
}
#[test]
fn validate_passes_for_valid_config() {
assert!(VectorConfig::default().validate().is_ok());
}
#[test]
fn validate_fails_for_bare_default_namespace() {
let c = VectorConfig {
default_namespace: Namespace::default_ns(),
..Default::default()
};
assert!(c.validate().is_err());
}
#[test]
fn validate_fails_for_zero_dimension() {
let c = VectorConfig {
dimension: Some(0),
..Default::default()
};
let err = c.validate().unwrap_err();
assert!(err.to_string().contains("dimension"));
}
#[test]
fn validate_fails_for_empty_table_name() {
let c = VectorConfig {
table_name: String::new(),
..Default::default()
};
assert!(c.validate().is_err());
}
#[test]
fn resolve_namespace_returns_provided() {
let c = VectorConfig::default();
let ns = Namespace::named("override");
assert_eq!(c.resolve_namespace(Some(&ns)), &ns);
}
#[test]
fn resolve_namespace_falls_back_to_default() {
let c = VectorConfig::with_namespace("default-ns");
assert_eq!(c.resolve_namespace(None).as_deref(), Some("default-ns"));
}
#[test]
fn check_dimension_passes_when_not_configured() {
let c = VectorConfig::default();
assert!(c.check_dimension(999).is_ok());
}
#[test]
fn check_dimension_passes_when_matching() {
let c = VectorConfig::with_dimension(384);
assert!(c.check_dimension(384).is_ok());
}
#[test]
fn check_dimension_fails_on_mismatch() {
let c = VectorConfig::with_dimension(384);
let err = c.check_dimension(512).unwrap_err();
assert!(matches!(err, StoreError::DimensionMismatch { expected: 384, actual: 512 }));
}
}