velesdb_core/
config_validation.rs1use crate::config::{ConfigError, VelesConfig};
6
7#[cfg(target_pointer_width = "64")]
23const MAX_VECTORS_PER_COLLECTION_CAP: usize = 10_000_000_000;
24#[cfg(not(target_pointer_width = "64"))]
25const MAX_VECTORS_PER_COLLECTION_CAP: usize = 4_000_000_000;
26const MAX_COLLECTIONS_CAP: usize = 1_000_000;
28const MAX_PAYLOAD_SIZE_CAP: usize = 1_073_741_824;
30const MAX_PERFECT_MODE_VECTORS_CAP: usize = 100_000_000;
32const QUERY_TIMEOUT_MS_CAP: u64 = 86_400_000;
37const MAX_LAYERS_CAP: usize = 64;
39const MMAP_CACHE_MB_CAP: usize = 1_048_576;
42const WORKERS_CAP: usize = 4_096;
45
46fn range_check_capacity(key: &str, value: usize, cap: usize) -> Result<(), ConfigError> {
48 if value == 0 || value > cap {
49 return Err(ConfigError::InvalidValue {
50 key: key.to_string(),
51 message: format!("value {value} is out of range [1, {cap}]"),
52 });
53 }
54 Ok(())
55}
56
57fn range_check_upper<T: PartialOrd + Copy + std::fmt::Display>(
61 key: &str,
62 value: T,
63 cap: T,
64) -> Result<(), ConfigError> {
65 if value > cap {
66 return Err(ConfigError::InvalidValue {
67 key: key.to_string(),
68 message: format!("value {value} is out of range [0, {cap}]"),
69 });
70 }
71 Ok(())
72}
73
74impl VelesConfig {
75 pub fn validate(&self) -> Result<(), ConfigError> {
81 self.validate_search()?;
82 self.validate_hnsw()?;
83 self.validate_limits()?;
84 self.validate_server()?;
85 self.validate_storage()?;
86 self.validate_logging()
87 }
88
89 fn validate_search(&self) -> Result<(), ConfigError> {
90 if let Some(ef) = self.search.ef_search {
91 if !(16..=4096).contains(&ef) {
92 return Err(ConfigError::InvalidValue {
93 key: "search.ef_search".to_string(),
94 message: format!("value {ef} is out of range [16, 4096]"),
95 });
96 }
97 }
98
99 if self.search.max_results == 0 || self.search.max_results > 10000 {
100 return Err(ConfigError::InvalidValue {
101 key: "search.max_results".to_string(),
102 message: format!(
103 "value {} is out of range [1, 10000]",
104 self.search.max_results
105 ),
106 });
107 }
108
109 range_check_upper(
112 "search.query_timeout_ms",
113 self.search.query_timeout_ms,
114 QUERY_TIMEOUT_MS_CAP,
115 )
116 }
117
118 fn validate_hnsw(&self) -> Result<(), ConfigError> {
119 if let Some(m) = self.hnsw.m {
120 if !(4..=128).contains(&m) {
121 return Err(ConfigError::InvalidValue {
122 key: "hnsw.m".to_string(),
123 message: format!("value {m} is out of range [4, 128]"),
124 });
125 }
126 }
127
128 if let Some(ef) = self.hnsw.ef_construction {
129 if !(100..=2000).contains(&ef) {
130 return Err(ConfigError::InvalidValue {
131 key: "hnsw.ef_construction".to_string(),
132 message: format!("value {ef} is out of range [100, 2000]"),
133 });
134 }
135 }
136
137 range_check_upper("hnsw.max_layers", self.hnsw.max_layers, MAX_LAYERS_CAP)
140 }
141
142 fn validate_limits(&self) -> Result<(), ConfigError> {
143 let limits = &self.limits;
144 range_check_capacity("limits.max_dimensions", limits.max_dimensions, 65536)?;
145 range_check_capacity(
146 "limits.max_vectors_per_collection",
147 limits.max_vectors_per_collection,
148 MAX_VECTORS_PER_COLLECTION_CAP,
149 )?;
150 range_check_capacity(
151 "limits.max_collections",
152 limits.max_collections,
153 MAX_COLLECTIONS_CAP,
154 )?;
155 range_check_capacity(
156 "limits.max_payload_size",
157 limits.max_payload_size,
158 MAX_PAYLOAD_SIZE_CAP,
159 )?;
160 range_check_capacity(
161 "limits.max_perfect_mode_vectors",
162 limits.max_perfect_mode_vectors,
163 MAX_PERFECT_MODE_VECTORS_CAP,
164 )
165 }
166
167 fn validate_server(&self) -> Result<(), ConfigError> {
168 if self.server.port < 1024 {
169 return Err(ConfigError::InvalidValue {
170 key: "server.port".to_string(),
171 message: format!("value {} must be >= 1024", self.server.port),
172 });
173 }
174
175 range_check_upper("server.workers", self.server.workers, WORKERS_CAP)
178 }
179
180 fn validate_storage(&self) -> Result<(), ConfigError> {
181 let valid_modes = ["mmap", "memory"];
182 if !valid_modes.contains(&self.storage.storage_mode.as_str()) {
183 return Err(ConfigError::InvalidValue {
184 key: "storage.storage_mode".to_string(),
185 message: format!(
186 "value '{}' is invalid, expected one of: {:?}",
187 self.storage.storage_mode, valid_modes
188 ),
189 });
190 }
191
192 range_check_capacity(
195 "storage.mmap_cache_mb",
196 self.storage.mmap_cache_mb,
197 MMAP_CACHE_MB_CAP,
198 )?;
199 Ok(())
200 }
201
202 fn validate_logging(&self) -> Result<(), ConfigError> {
203 let valid_levels = ["error", "warn", "info", "debug", "trace"];
204 if !valid_levels.contains(&self.logging.level.as_str()) {
205 return Err(ConfigError::InvalidValue {
206 key: "logging.level".to_string(),
207 message: format!(
208 "value '{}' is invalid, expected one of: {:?}",
209 self.logging.level, valid_levels
210 ),
211 });
212 }
213 Ok(())
214 }
215}