oxify_storage/
validation.rs1use crate::{Result, StorageError};
7use std::fmt::Display;
8
9pub fn validate_positive<T: PartialOrd + Default + Display>(
19 value: T,
20 field_name: &str,
21) -> Result<T> {
22 if value <= T::default() {
23 return Err(StorageError::validation(format!(
24 "{} must be positive, got {}",
25 field_name, value
26 )));
27 }
28 Ok(value)
29}
30
31pub fn validate_optional_positive<T: PartialOrd + Default + Display + Copy>(
35 value: Option<T>,
36 field_name: &str,
37) -> Result<Option<T>> {
38 if let Some(v) = value {
39 validate_positive(v, field_name)?;
40 }
41 Ok(value)
42}
43
44pub fn validate_non_empty_string(value: &str, field_name: &str) -> Result<()> {
54 if value.trim().is_empty() {
55 return Err(StorageError::validation(format!(
56 "{} cannot be empty",
57 field_name
58 )));
59 }
60 Ok(())
61}
62
63pub fn validate_collection_size<T>(
73 collection: &[T],
74 max_size: usize,
75 collection_name: &str,
76) -> Result<()> {
77 if collection.len() > max_size {
78 return Err(StorageError::validation(format!(
79 "{} has {} items, which exceeds the maximum of {}",
80 collection_name,
81 collection.len(),
82 max_size
83 )));
84 }
85 Ok(())
86}
87
88pub struct QuotaLimitValidator {
108 errors: Vec<String>,
109}
110
111impl QuotaLimitValidator {
112 pub fn new() -> Self {
114 Self { errors: Vec::new() }
115 }
116
117 pub fn validate_limit(mut self, value: Option<i32>, name: &str) -> Self {
119 if let Some(limit) = value {
120 if limit <= 0 {
121 self.errors
122 .push(format!("{} must be positive, got {}", name, limit));
123 }
124 }
125 self
126 }
127
128 pub fn validate_limit_i64(mut self, value: Option<i64>, name: &str) -> Self {
130 if let Some(limit) = value {
131 if limit <= 0 {
132 self.errors
133 .push(format!("{} must be positive, got {}", name, limit));
134 }
135 }
136 self
137 }
138
139 pub fn build(self) -> Result<()> {
143 if self.errors.is_empty() {
144 Ok(())
145 } else {
146 Err(StorageError::validation(self.errors.join("; ")))
147 }
148 }
149}
150
151impl Default for QuotaLimitValidator {
152 fn default() -> Self {
153 Self::new()
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 #[test]
162 fn test_validate_positive_i32() {
163 assert!(validate_positive(10, "test").is_ok());
164 assert!(validate_positive(1, "test").is_ok());
165
166 let result = validate_positive(0, "test");
167 assert!(result.is_err());
168
169 let result = validate_positive(-5, "test");
170 assert!(result.is_err());
171 }
172
173 #[test]
174 fn test_validate_positive_i64() {
175 assert!(validate_positive(100i64, "test").is_ok());
176 assert!(validate_positive(0i64, "test").is_err());
177 assert!(validate_positive(-10i64, "test").is_err());
178 }
179
180 #[test]
181 fn test_validate_optional_positive() {
182 assert!(validate_optional_positive(Some(10), "test").is_ok());
183 assert!(validate_optional_positive(None::<i32>, "test").is_ok());
184 assert!(validate_optional_positive(Some(0), "test").is_err());
185 assert!(validate_optional_positive(Some(-5), "test").is_err());
186 }
187
188 #[test]
189 fn test_validate_non_empty_string() {
190 assert!(validate_non_empty_string("test", "field").is_ok());
191 assert!(validate_non_empty_string("a", "field").is_ok());
192
193 let result = validate_non_empty_string("", "field");
194 assert!(result.is_err());
195 assert!(result.unwrap_err().to_string().contains("cannot be empty"));
196
197 let result = validate_non_empty_string(" ", "field");
198 assert!(result.is_err());
199 }
200
201 #[test]
202 fn test_validate_collection_size() {
203 let items = vec![1, 2, 3];
204 assert!(validate_collection_size(&items, 5, "items").is_ok());
205 assert!(validate_collection_size(&items, 3, "items").is_ok());
206
207 let result = validate_collection_size(&items, 2, "items");
208 assert!(result.is_err());
209 assert!(result
210 .unwrap_err()
211 .to_string()
212 .contains("exceeds the maximum"));
213 }
214
215 #[test]
216 fn test_quota_limit_validator_valid() {
217 let result = QuotaLimitValidator::new()
218 .validate_limit(Some(100), "max_executions")
219 .validate_limit(Some(50), "max_tokens")
220 .validate_limit_i64(Some(1000), "max_cost")
221 .build();
222 assert!(result.is_ok());
223 }
224
225 #[test]
226 fn test_quota_limit_validator_with_none() {
227 let result = QuotaLimitValidator::new()
228 .validate_limit(None, "max_executions")
229 .validate_limit(Some(50), "max_tokens")
230 .build();
231 assert!(result.is_ok());
232 }
233
234 #[test]
235 fn test_quota_limit_validator_invalid() {
236 let result = QuotaLimitValidator::new()
237 .validate_limit(Some(-1), "max_executions")
238 .validate_limit(Some(0), "max_tokens")
239 .build();
240
241 assert!(result.is_err());
242 let error_msg = result.unwrap_err().to_string();
243 assert!(error_msg.contains("max_executions"));
244 assert!(error_msg.contains("max_tokens"));
245 }
246
247 #[test]
248 fn test_quota_limit_validator_multiple_errors() {
249 let result = QuotaLimitValidator::new()
250 .validate_limit(Some(-1), "field1")
251 .validate_limit(Some(0), "field2")
252 .validate_limit_i64(Some(-100), "field3")
253 .build();
254
255 assert!(result.is_err());
256 let error_msg = result.unwrap_err().to_string();
257 assert!(error_msg.contains("field1"));
258 assert!(error_msg.contains("field2"));
259 assert!(error_msg.contains("field3"));
260 }
261}