use serde::{Deserialize, Serialize};
use rskit_errors::{AppError, AppResult, ErrorCode};
use crate::SimilarityMetric;
pub const DEFAULT_MAX_SEARCH_LIMIT: usize = 1_000;
pub const DEFAULT_MAX_VECTOR_DIMENSIONS: usize = 32_768;
pub const DEFAULT_MAX_PAYLOAD_FIELDS: usize = 128;
pub const DEFAULT_MAX_PAYLOAD_BYTES: usize = 64 * 1024;
pub const DEFAULT_MAX_FILTER_CONDITIONS: usize = 32;
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct VectorStoreConfig {
#[serde(default = "default_backend")]
pub backend: String,
#[serde(default)]
pub memory: MemoryVectorStoreConfig,
#[serde(default)]
pub limits: VectorStoreLimits,
}
impl Default for VectorStoreConfig {
fn default() -> Self {
Self {
backend: default_backend(),
memory: MemoryVectorStoreConfig::default(),
limits: VectorStoreLimits::default(),
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
#[non_exhaustive]
pub struct VectorStoreLimits {
#[serde(default = "default_max_search_limit")]
pub max_search_limit: usize,
#[serde(default = "default_max_vector_dimensions")]
pub max_vector_dimensions: usize,
#[serde(default = "default_max_payload_fields")]
pub max_payload_fields: usize,
#[serde(default = "default_max_payload_bytes")]
pub max_payload_bytes: usize,
#[serde(default = "default_max_filter_conditions")]
pub max_filter_conditions: usize,
}
impl VectorStoreLimits {
#[must_use]
pub const fn new() -> Self {
Self {
max_search_limit: DEFAULT_MAX_SEARCH_LIMIT,
max_vector_dimensions: DEFAULT_MAX_VECTOR_DIMENSIONS,
max_payload_fields: DEFAULT_MAX_PAYLOAD_FIELDS,
max_payload_bytes: DEFAULT_MAX_PAYLOAD_BYTES,
max_filter_conditions: DEFAULT_MAX_FILTER_CONDITIONS,
}
}
#[must_use]
pub const fn with_max_search_limit(mut self, max_search_limit: usize) -> Self {
self.max_search_limit = max_search_limit;
self
}
#[must_use]
pub const fn with_max_vector_dimensions(mut self, max_vector_dimensions: usize) -> Self {
self.max_vector_dimensions = max_vector_dimensions;
self
}
#[must_use]
pub const fn with_max_payload_fields(mut self, max_payload_fields: usize) -> Self {
self.max_payload_fields = max_payload_fields;
self
}
#[must_use]
pub const fn with_max_payload_bytes(mut self, max_payload_bytes: usize) -> Self {
self.max_payload_bytes = max_payload_bytes;
self
}
#[must_use]
pub const fn with_max_filter_conditions(mut self, max_filter_conditions: usize) -> Self {
self.max_filter_conditions = max_filter_conditions;
self
}
pub fn validate_dimensions(&self, dimensions: usize) -> AppResult<()> {
if self.max_vector_dimensions == 0 {
return Err(AppError::new(
ErrorCode::InvalidInput,
"max_vector_dimensions must be at least 1",
));
}
if dimensions == 0 || dimensions > self.max_vector_dimensions {
return Err(AppError::new(
ErrorCode::InvalidInput,
format!(
"vector dimensions must be between 1 and {}",
self.max_vector_dimensions
),
));
}
Ok(())
}
pub fn validate_search_limit(&self, limit: usize) -> AppResult<()> {
if self.max_search_limit == 0 {
return Err(AppError::new(
ErrorCode::InvalidInput,
"max_search_limit must be at least 1",
));
}
if limit == 0 || limit > self.max_search_limit {
return Err(AppError::new(
ErrorCode::InvalidInput,
format!(
"vector search limit must be between 1 and {}",
self.max_search_limit
),
));
}
Ok(())
}
}
impl Default for VectorStoreLimits {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct MemoryVectorStoreConfig {
#[serde(default)]
pub metric: SimilarityMetric,
}
impl Default for MemoryVectorStoreConfig {
fn default() -> Self {
Self {
metric: SimilarityMetric::Cosine,
}
}
}
fn default_backend() -> String {
"memory".to_owned()
}
const fn default_max_search_limit() -> usize {
DEFAULT_MAX_SEARCH_LIMIT
}
const fn default_max_vector_dimensions() -> usize {
DEFAULT_MAX_VECTOR_DIMENSIONS
}
const fn default_max_payload_fields() -> usize {
DEFAULT_MAX_PAYLOAD_FIELDS
}
const fn default_max_payload_bytes() -> usize {
DEFAULT_MAX_PAYLOAD_BYTES
}
const fn default_max_filter_conditions() -> usize {
DEFAULT_MAX_FILTER_CONDITIONS
}
#[cfg(test)]
mod tests {
use rskit_errors::ErrorCode;
use super::*;
#[test]
fn validate_dimensions_rejects_zero_configured_max_with_clear_error() {
let err = VectorStoreLimits::new()
.with_max_vector_dimensions(0)
.validate_dimensions(1)
.unwrap_err();
assert_eq!(err.code(), ErrorCode::InvalidInput);
assert!(err.message().contains("max_vector_dimensions"));
assert!(!err.message().contains("between 1 and 0"));
}
#[test]
fn validate_search_limit_rejects_zero_configured_max_with_clear_error() {
let err = VectorStoreLimits::new()
.with_max_search_limit(0)
.validate_search_limit(1)
.unwrap_err();
assert_eq!(err.code(), ErrorCode::InvalidInput);
assert!(err.message().contains("max_search_limit"));
assert!(!err.message().contains("between 1 and 0"));
}
}