Skip to main content

rskit_vectorstore/
config.rs

1//! Vector store configuration types.
2
3use serde::{Deserialize, Serialize};
4
5use rskit_errors::{AppError, AppResult, ErrorCode};
6
7use crate::SimilarityMetric;
8
9/// Default maximum number of nearest-neighbor results returned by one search.
10pub const DEFAULT_MAX_SEARCH_LIMIT: usize = 1_000;
11/// Default maximum vector dimensionality accepted by core stores.
12pub const DEFAULT_MAX_VECTOR_DIMENSIONS: usize = 32_768;
13/// Default maximum number of scalar payload fields per point.
14pub const DEFAULT_MAX_PAYLOAD_FIELDS: usize = 128;
15/// Default maximum approximate scalar bytes per point payload or search filter.
16pub const DEFAULT_MAX_PAYLOAD_BYTES: usize = 64 * 1024;
17/// Default maximum exact-match filter conditions per search.
18pub const DEFAULT_MAX_FILTER_CONDITIONS: usize = 32;
19
20/// Config-driven vector store backend selection.
21#[derive(Debug, Clone, Deserialize, Serialize)]
22pub struct VectorStoreConfig {
23    /// Backend name looked up in an injected [`crate::VectorStoreRegistry`].
24    #[serde(default = "default_backend")]
25    pub backend: String,
26    /// In-memory backend options.
27    #[serde(default)]
28    pub memory: MemoryVectorStoreConfig,
29    /// Shared safety limits applied by core stores and adapters.
30    #[serde(default)]
31    pub limits: VectorStoreLimits,
32}
33
34impl Default for VectorStoreConfig {
35    fn default() -> Self {
36        Self {
37            backend: default_backend(),
38            memory: MemoryVectorStoreConfig::default(),
39            limits: VectorStoreLimits::default(),
40        }
41    }
42}
43
44/// Safety limits for vector operations, untrusted payloads, and filters.
45#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
46#[non_exhaustive]
47pub struct VectorStoreLimits {
48    /// Maximum `limit` accepted by `search`.
49    #[serde(default = "default_max_search_limit")]
50    pub max_search_limit: usize,
51    /// Maximum vector dimensions accepted for collection/query vectors.
52    #[serde(default = "default_max_vector_dimensions")]
53    pub max_vector_dimensions: usize,
54    /// Maximum scalar fields allowed in a point payload.
55    #[serde(default = "default_max_payload_fields")]
56    pub max_payload_fields: usize,
57    /// Maximum approximate scalar bytes allowed in point payloads and search filters.
58    #[serde(default = "default_max_payload_bytes")]
59    pub max_payload_bytes: usize,
60    /// Maximum exact-match filter conditions accepted by `search`.
61    #[serde(default = "default_max_filter_conditions")]
62    pub max_filter_conditions: usize,
63}
64
65impl VectorStoreLimits {
66    /// Create limits with production-safe defaults.
67    #[must_use]
68    pub const fn new() -> Self {
69        Self {
70            max_search_limit: DEFAULT_MAX_SEARCH_LIMIT,
71            max_vector_dimensions: DEFAULT_MAX_VECTOR_DIMENSIONS,
72            max_payload_fields: DEFAULT_MAX_PAYLOAD_FIELDS,
73            max_payload_bytes: DEFAULT_MAX_PAYLOAD_BYTES,
74            max_filter_conditions: DEFAULT_MAX_FILTER_CONDITIONS,
75        }
76    }
77
78    /// Override the maximum search result count.
79    #[must_use]
80    pub const fn with_max_search_limit(mut self, max_search_limit: usize) -> Self {
81        self.max_search_limit = max_search_limit;
82        self
83    }
84
85    /// Override the maximum vector dimensionality.
86    #[must_use]
87    pub const fn with_max_vector_dimensions(mut self, max_vector_dimensions: usize) -> Self {
88        self.max_vector_dimensions = max_vector_dimensions;
89        self
90    }
91
92    /// Override the maximum number of payload fields.
93    #[must_use]
94    pub const fn with_max_payload_fields(mut self, max_payload_fields: usize) -> Self {
95        self.max_payload_fields = max_payload_fields;
96        self
97    }
98
99    /// Override the maximum approximate byte count for payloads and filters.
100    #[must_use]
101    pub const fn with_max_payload_bytes(mut self, max_payload_bytes: usize) -> Self {
102        self.max_payload_bytes = max_payload_bytes;
103        self
104    }
105
106    /// Override the maximum number of exact-match filter conditions.
107    #[must_use]
108    pub const fn with_max_filter_conditions(mut self, max_filter_conditions: usize) -> Self {
109        self.max_filter_conditions = max_filter_conditions;
110        self
111    }
112
113    /// Validate a vector dimension count against the configured bounds.
114    pub fn validate_dimensions(&self, dimensions: usize) -> AppResult<()> {
115        if self.max_vector_dimensions == 0 {
116            return Err(AppError::new(
117                ErrorCode::InvalidInput,
118                "max_vector_dimensions must be at least 1",
119            ));
120        }
121        if dimensions == 0 || dimensions > self.max_vector_dimensions {
122            return Err(AppError::new(
123                ErrorCode::InvalidInput,
124                format!(
125                    "vector dimensions must be between 1 and {}",
126                    self.max_vector_dimensions
127                ),
128            ));
129        }
130        Ok(())
131    }
132
133    /// Validate a search result limit against the configured bounds.
134    pub fn validate_search_limit(&self, limit: usize) -> AppResult<()> {
135        if self.max_search_limit == 0 {
136            return Err(AppError::new(
137                ErrorCode::InvalidInput,
138                "max_search_limit must be at least 1",
139            ));
140        }
141        if limit == 0 || limit > self.max_search_limit {
142            return Err(AppError::new(
143                ErrorCode::InvalidInput,
144                format!(
145                    "vector search limit must be between 1 and {}",
146                    self.max_search_limit
147                ),
148            ));
149        }
150        Ok(())
151    }
152}
153
154impl Default for VectorStoreLimits {
155    fn default() -> Self {
156        Self::new()
157    }
158}
159
160/// In-memory vector store options.
161#[derive(Debug, Clone, Deserialize, Serialize)]
162pub struct MemoryVectorStoreConfig {
163    /// Default metric used for collections created by the memory backend.
164    #[serde(default)]
165    pub metric: SimilarityMetric,
166}
167
168impl Default for MemoryVectorStoreConfig {
169    fn default() -> Self {
170        Self {
171            metric: SimilarityMetric::Cosine,
172        }
173    }
174}
175
176fn default_backend() -> String {
177    "memory".to_owned()
178}
179
180const fn default_max_search_limit() -> usize {
181    DEFAULT_MAX_SEARCH_LIMIT
182}
183
184const fn default_max_vector_dimensions() -> usize {
185    DEFAULT_MAX_VECTOR_DIMENSIONS
186}
187
188const fn default_max_payload_fields() -> usize {
189    DEFAULT_MAX_PAYLOAD_FIELDS
190}
191
192const fn default_max_payload_bytes() -> usize {
193    DEFAULT_MAX_PAYLOAD_BYTES
194}
195
196const fn default_max_filter_conditions() -> usize {
197    DEFAULT_MAX_FILTER_CONDITIONS
198}
199
200#[cfg(test)]
201mod tests {
202    use rskit_errors::ErrorCode;
203
204    use super::*;
205
206    #[test]
207    fn validate_dimensions_rejects_zero_configured_max_with_clear_error() {
208        let err = VectorStoreLimits::new()
209            .with_max_vector_dimensions(0)
210            .validate_dimensions(1)
211            .unwrap_err();
212
213        assert_eq!(err.code(), ErrorCode::InvalidInput);
214        assert!(err.message().contains("max_vector_dimensions"));
215        assert!(!err.message().contains("between 1 and 0"));
216    }
217
218    #[test]
219    fn validate_search_limit_rejects_zero_configured_max_with_clear_error() {
220        let err = VectorStoreLimits::new()
221            .with_max_search_limit(0)
222            .validate_search_limit(1)
223            .unwrap_err();
224
225        assert_eq!(err.code(), ErrorCode::InvalidInput);
226        assert!(err.message().contains("max_search_limit"));
227        assert!(!err.message().contains("between 1 and 0"));
228    }
229}