validate_ro/
lib.rs

1//! # Validation Library
2//!
3//! A flexible, extensible validation framework with support for:
4//! - Synchronous and asynchronous validation
5//! - Complex nested field validation
6//! - Custom validation rules
7//! - MongoDB integration for unique checks
8//! - Default values and error accumulation
9//!
10//! ## Core Concepts
11//!
12//! 1. **Validators**: Implement the `Validator` trait to create validation rules
13//! 2. **Rules**: Combine multiple validators with optional default values
14//! 3. **FormValidator**: Validate complete forms/objects with field-level rules
15//!
16//! ## Example: Basic Usage
17//!
18//! ```rust
19//! use validate_ro::{Rules, FormValidator};
20//! use serde_json::json;
21//! use validate_ro::rules::Rule;
22//!
23//! // Create validation rules
24//! let email_rule = Rules::new()
25//!     .add(Rule::required())
26//!     .add(Rule::email(None));
27//!
28//! let age_rule = Rules::new()
29//!     .add(Rule::integer())
30//!     .add(Rule::min_value(18.0))
31//!     .default(json!(21)); // Default value if null
32//!
33//! // Build form validator
34//! let validator = FormValidator::new()
35//!     .add("email", email_rule)
36//!     .add("age", age_rule);
37//!
38//! // Validate data
39//! let data = json!({"email": "test@example.com"});
40//! match validator.validate(&data) {
41//!     Ok(valid_data) => {
42//!         // age will be 21 (default value)
43//!         println!("Valid data: {:?}", valid_data);
44//!     },
45//!     Err(errors) => {
46//!         eprintln!("Validation errors: {:?}", errors);
47//!     }
48//! }
49//! ```
50
51use std::any::Any;
52use std::collections::HashMap;
53use std::sync::Arc;
54use async_trait::async_trait;
55use mongodb::bson::{to_bson, Document};
56use mongodb::{bson, Database};
57use mongodb::bson::ser::Error;
58use serde_json::Value;
59use crate::error::ValidationError;
60use crate::traits::{ValidationResult, Validator};
61
62pub mod rules;
63pub mod traits;
64pub mod error;
65
66/// Container for multiple validators with optional default value
67///
68/// # Examples
69///
70/// ```
71/// use validate_ro::{Rules};
72/// use serde_json::json;
73/// use validate_ro::rules::Rule;
74///
75/// let rule = Rules::new()
76///     .add(Rule::required())
77///     .add(Rule::min_length(8))
78///     .default(json!("default"));
79/// ```
80pub struct Rules {
81    validators: Vec<Box<dyn Validator+ Send + Sync>>,
82    default_value: Option<Value>,
83}
84
85impl Rules {
86    /// Creates a new empty Rules container
87    pub fn new() -> Self {
88        Self {
89            validators: Vec::new(),
90            default_value: None,
91        }
92    }
93
94    /// Adds a validator to the rules chain
95    pub fn add<V: Validator + 'static>(mut self, validator: V) -> Self {
96        self.validators.push(Box::new(validator));
97        self
98    }
99
100    /// Sets a default value that will be used when input is null
101    pub fn default(mut self, default: Value) -> Self {
102        self.default_value = Some(default);
103        self
104    }
105}
106
107#[async_trait]
108impl Validator for Rules {
109    fn validate(&self, value: &Value) -> ValidationResult {
110        let value = if value.is_null() && self.default_value.is_some() {
111            self.default_value.as_ref().unwrap()
112        } else {
113            value
114        };
115        for validator in &self.validators {
116            validator.validate(value)?;
117        }
118        Ok(())
119    }
120
121    async fn validate_async(&self, _db: &Arc<Database>, value: &Value) -> ValidationResult {
122        let value = if value.is_null() && self.default_value.is_some() {
123            self.default_value.as_ref().unwrap()
124        } else {
125            value
126        };
127        for validator in &self.validators {
128            validator.validate_async(_db,value).await?;
129        }
130        Ok(())
131    }
132
133    fn as_any(&self) -> &dyn Any {
134        self
135    }
136}
137
138
139/// Validates complete forms/objects with field-level rules
140///
141/// Supports:
142/// - Nested field paths (e.g., "user.address.street")
143/// - Early termination on first error
144/// - Async validation with MongoDB
145///
146/// # Example
147///
148/// ```
149/// use validate_ro::{FormValidator};
150/// use validate_ro::rules::Rule;
151///
152/// let validator = FormValidator::new()
153///     .add("username", Rule::required())
154///     .add("profile.age", Rule::integer());
155/// ```
156pub struct FormValidator {
157    break_on_error:bool,
158    field_validators: HashMap<String, Box<dyn Validator+ Send + Sync>>,
159}
160
161impl FormValidator {
162    /// Creates a new validator that collects all errors
163    pub fn new() -> Self {
164        Self {
165            break_on_error:false,
166            field_validators: HashMap::new(),
167        }
168    }
169
170    /// Adds validation rules for a field
171    ///
172    /// # Arguments
173    ///
174    /// * `field_name` - Field path (supports dot notation for nested fields)
175    /// * `validator` - Validation rules
176    pub fn add(
177        mut self,
178        field_name: &str,
179        validator: impl Validator + 'static,
180    ) -> Self {
181        self.field_validators
182            .insert(field_name.to_string(), Box::new(validator));
183        self
184    }
185
186    /// Validates form data synchronously
187    ///
188    /// Returns either:
189    /// - Ok(Document) with validated values (including defaults)
190    /// - Err(HashMap) with field names and error lists
191    pub fn validate(
192        &self,
193        form_data: &Value,
194    ) -> Result<Document, HashMap<String,Vec<ValidationError>>> {
195        let mut errors = HashMap::new();
196        let mut valid_data = HashMap::new();
197
198        for (field_name, validator) in &self.field_validators {
199            let value = if field_name.contains('.') {
200                let mut current = form_data;
201                for part in field_name.split('.') {
202                    current = current.get(part).unwrap_or(&Value::Null);
203                }
204                current
205            } else {
206                form_data.get(field_name).unwrap_or(&Value::Null)
207            };
208            let processed_value = if let Some(rules) = validator.as_any().downcast_ref::<Rules>() {
209                if value.is_null() && rules.default_value.is_some() {
210                    rules.default_value.as_ref().unwrap()
211                } else {
212                    value
213                }
214            } else {
215                value
216            };
217            if let Err(err) = validator.validate(processed_value) {
218                match errors.get_mut(field_name){
219                    None => {
220                        errors.insert(field_name.clone(),vec![err]);
221                    }
222                    Some(a) => {
223                        a.push(err);
224                    }
225                }
226
227                if self.break_on_error {
228                    break;
229                }
230            } else {
231                valid_data.insert(field_name.clone(), processed_value.clone());
232            }
233        }
234
235        if errors.is_empty() {
236            match hashmap_to_document(valid_data){
237                Ok(a) => {
238                    Ok(a)
239                }
240                Err(e) => {
241                    errors.insert("data".to_string(),vec![ValidationError::Custom(e.to_string())]);
242                    Err(errors)
243                }
244            }
245        } else {
246            Err(errors)
247        }
248    }
249
250    /// Validates form data asynchronously with MongoDB access
251    ///
252    /// Used for validators that require database checks (like uniqueness)
253    /// Returns either:
254    /// - Ok(Document) with validated values (including defaults)
255    /// - Err(HashMap) with field names and error lists
256    pub async fn validate_async(
257        &self,
258        db:&Arc<Database>,
259        form_data: &Value,
260    ) -> Result<Document, HashMap<String,Vec<ValidationError>>> {
261        let mut errors = HashMap::new();
262        let mut valid_data = HashMap::new();
263
264        for (field_name, validator) in &self.field_validators {
265            let value = if field_name.contains('.') {
266                let mut current = form_data;
267                for part in field_name.split('.') {
268                    current = current.get(part).unwrap_or(&Value::Null);
269                }
270                current
271            } else {
272                form_data.get(field_name).unwrap_or(&Value::Null)
273            };
274            let processed_value = if let Some(rules) = validator.as_any().downcast_ref::<Rules>() {
275                if value.is_null() && rules.default_value.is_some() {
276                    rules.default_value.as_ref().unwrap()
277                } else {
278                    value
279                }
280            } else {
281                value
282            };
283
284            if let Err(err) = validator.validate_async(db,processed_value).await {
285                match errors.get_mut(field_name){
286                    None => {
287                        errors.insert(field_name.clone(),vec![err]);
288                    }
289                    Some(a) => {
290                        a.push(err);
291                    }
292                }
293                if self.break_on_error {
294                    break;
295                }
296            } else {
297                valid_data.insert(field_name.clone(), processed_value.clone());
298            }
299        }
300
301        if errors.is_empty() {
302            match hashmap_to_document(valid_data){
303                Ok(a) => {
304                    Ok(a)
305                }
306                Err(e) => {
307                    errors.insert("data".to_string(),vec![ValidationError::Custom(e.to_string())]);
308                    Err(errors)
309                }
310            }
311        } else {
312            Err(errors)
313        }
314    }
315
316    pub fn break_on_error(&mut self, break_on_error: bool) {
317        self.break_on_error = break_on_error;
318    }
319}
320fn hashmap_to_document(input: HashMap<String, Value>) -> Result<Document, bson::ser::Error> {
321    let mut doc = Document::new();
322
323    for (key, value) in input {
324        let bson_value = to_bson(&value)?;
325
326        doc.insert(key, bson_value);
327    }
328
329    Ok(doc)
330}