1use serde::{Deserialize, Serialize};
3use std::{num::NonZeroUsize, path::PathBuf};
4
5use crate::error::{VectorError, VectorResult};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct VectorConfig {
10 pub db_path: PathBuf,
12 pub index_dir: PathBuf,
14 pub embedding_service_url: String,
16 pub default_dimensions: usize,
18 pub ef_construction: usize,
20 pub m_connections: usize,
22 pub ef_search: usize,
24 pub max_elements: usize,
26 pub cache_size: usize,
28 pub batch_size: usize,
30 pub embedding_timeout_ms: u64,
32 pub num_threads: usize,
34 pub default_workspace_id: String,
36 pub require_workspace_id: bool,
38 pub api_key_store_path: PathBuf,
40 pub rate_limit_rps: u32,
42 pub require_auth: bool,
44}
45
46impl Default for VectorConfig {
47 fn default() -> Self {
48 VectorConfig {
49 db_path: PathBuf::from("claw_vector.db"),
50 index_dir: PathBuf::from("claw_vector_indices"),
51 embedding_service_url: "http://localhost:50051".into(),
52 default_dimensions: 384,
53 ef_construction: 200,
54 m_connections: 16,
55 ef_search: 50,
56 max_elements: 1_000_000,
57 cache_size: 10_000,
58 batch_size: 64,
59 embedding_timeout_ms: 5_000,
60 num_threads: std::thread::available_parallelism()
61 .unwrap_or(NonZeroUsize::new(4).unwrap())
62 .get(),
63 default_workspace_id: "default".into(),
64 require_workspace_id: !cfg!(test),
65 api_key_store_path: PathBuf::from("claw_vector_auth.db"),
66 rate_limit_rps: 100,
67 require_auth: !cfg!(test),
68 }
69 }
70}
71
72impl VectorConfig {
73 pub fn builder() -> VectorConfigBuilder {
75 VectorConfigBuilder::default()
76 }
77
78 pub fn from_env() -> Self {
90 let mut cfg = VectorConfig::default();
91 if let Ok(v) = std::env::var("CLAW_VECTOR_DB_PATH") {
92 cfg.db_path = PathBuf::from(v);
93 }
94 if let Ok(v) = std::env::var("CLAW_VECTOR_INDEX_DIR") {
95 cfg.index_dir = PathBuf::from(v);
96 }
97 if let Ok(v) = std::env::var("CLAW_EMBEDDING_URL") {
98 cfg.embedding_service_url = v;
99 }
100 if let Ok(v) = std::env::var("CLAW_DEFAULT_WORKSPACE_ID") {
101 cfg.default_workspace_id = v;
102 }
103 if let Ok(v) = std::env::var("CLAW_REQUIRE_WORKSPACE_ID") {
104 cfg.require_workspace_id =
105 matches!(v.to_ascii_lowercase().as_str(), "1" | "true" | "yes");
106 }
107 if let Ok(v) = std::env::var("CLAW_API_KEY_STORE_PATH") {
108 cfg.api_key_store_path = PathBuf::from(v);
109 }
110 if let Ok(v) = std::env::var("CLAW_RATE_LIMIT_RPS") {
111 if let Ok(parsed) = v.parse::<u32>() {
112 cfg.rate_limit_rps = parsed.max(1);
113 }
114 }
115 if let Ok(v) = std::env::var("CLAW_REQUIRE_AUTH") {
116 cfg.require_auth = matches!(v.to_ascii_lowercase().as_str(), "1" | "true" | "yes");
117 }
118 cfg
119 }
120}
121
122#[derive(Debug, Clone, Default)]
126pub struct VectorConfigBuilder {
127 inner: VectorConfig,
128}
129
130impl VectorConfigBuilder {
131 pub fn db_path(mut self, path: impl Into<PathBuf>) -> Self {
133 self.inner.db_path = path.into();
134 self
135 }
136
137 pub fn index_dir(mut self, dir: impl Into<PathBuf>) -> Self {
139 self.inner.index_dir = dir.into();
140 self
141 }
142
143 pub fn embedding_service_url(mut self, url: impl Into<String>) -> Self {
145 self.inner.embedding_service_url = url.into();
146 self
147 }
148
149 pub fn default_dimensions(mut self, dims: usize) -> Self {
151 self.inner.default_dimensions = dims;
152 self
153 }
154
155 pub fn ef_construction(mut self, ef: usize) -> Self {
157 self.inner.ef_construction = ef;
158 self
159 }
160
161 pub fn m_connections(mut self, m: usize) -> Self {
163 self.inner.m_connections = m;
164 self
165 }
166
167 pub fn ef_search(mut self, ef: usize) -> Self {
169 self.inner.ef_search = ef;
170 self
171 }
172
173 pub fn max_elements(mut self, n: usize) -> Self {
175 self.inner.max_elements = n;
176 self
177 }
178
179 pub fn cache_size(mut self, n: usize) -> Self {
181 self.inner.cache_size = n;
182 self
183 }
184
185 pub fn batch_size(mut self, n: usize) -> Self {
187 self.inner.batch_size = n;
188 self
189 }
190
191 pub fn embedding_timeout_ms(mut self, ms: u64) -> Self {
193 self.inner.embedding_timeout_ms = ms;
194 self
195 }
196
197 pub fn num_threads(mut self, n: usize) -> Self {
199 self.inner.num_threads = n;
200 self
201 }
202
203 pub fn default_workspace_id(mut self, workspace_id: impl Into<String>) -> Self {
205 self.inner.default_workspace_id = workspace_id.into();
206 self
207 }
208
209 pub fn require_workspace_id(mut self, require_workspace_id: bool) -> Self {
211 self.inner.require_workspace_id = require_workspace_id;
212 self
213 }
214
215 pub fn api_key_store_path(mut self, path: impl Into<PathBuf>) -> Self {
217 self.inner.api_key_store_path = path.into();
218 self
219 }
220
221 pub fn rate_limit_rps(mut self, rps: u32) -> Self {
223 self.inner.rate_limit_rps = rps.max(1);
224 self
225 }
226
227 pub fn require_auth(mut self, require_auth: bool) -> Self {
229 self.inner.require_auth = require_auth;
230 self
231 }
232
233 pub fn build(self) -> VectorResult<VectorConfig> {
240 let cfg = self.inner;
241 if cfg.default_dimensions < 1 {
242 return Err(VectorError::Config(
243 "default_dimensions must be >= 1".into(),
244 ));
245 }
246 if cfg.m_connections < 2 {
247 return Err(VectorError::Config("m_connections must be >= 2".into()));
248 }
249 if cfg.ef_construction < cfg.m_connections {
250 return Err(VectorError::Config(
251 "ef_construction must be >= m_connections".into(),
252 ));
253 }
254 if cfg.default_workspace_id.trim().is_empty() {
255 return Err(VectorError::Config(
256 "default_workspace_id must not be empty".into(),
257 ));
258 }
259 if cfg.rate_limit_rps == 0 {
260 return Err(VectorError::Config("rate_limit_rps must be > 0".into()));
261 }
262 Ok(cfg)
263 }
264}