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 serde_json::Value;
58use crate::error::ValidationError;
59use crate::traits::{ValidationResult, Validator};
60
61pub mod rules;
62pub mod traits;
63pub mod error;
64
65/// Container for multiple validators with optional default value
66///
67/// # Examples
68///
69/// ```
70/// use validate_ro::{Rules};
71/// use serde_json::json;
72/// use validate_ro::rules::Rule;
73///
74/// let rule = Rules::new()
75///     .add(Rule::required())
76///     .add(Rule::min_length(8))
77///     .default(json!("default"));
78/// ```
79pub struct Rules {
80    validators: Vec<Box<dyn Validator+ Send + Sync>>,
81    default_value: Option<Value>,
82}
83
84impl Rules {
85    /// Creates a new empty Rules container
86    pub fn new() -> Self {
87        Self {
88            validators: Vec::new(),
89            default_value: None,
90        }
91    }
92
93    /// Adds a validator to the rules chain
94    pub fn add<V: Validator + 'static>(mut self, validator: V) -> Self {
95        self.validators.push(Box::new(validator));
96        self
97    }
98
99    /// Sets a default value that will be used when input is null
100    pub fn default(mut self, default: Value) -> Self {
101        self.default_value = Some(default);
102        self
103    }
104}
105
106#[async_trait]
107impl Validator for Rules {
108    fn validate(&self, value: &Value) -> ValidationResult {
109        let value = if value.is_null() && self.default_value.is_some() {
110            self.default_value.as_ref().unwrap()
111        } else {
112            value
113        };
114        for validator in &self.validators {
115            validator.validate(value)?;
116        }
117        Ok(())
118    }
119
120    async fn validate_async(&self, _db: &Database, value: &Value) -> ValidationResult {
121        let value = if value.is_null() && self.default_value.is_some() {
122            self.default_value.as_ref().unwrap()
123        } else {
124            value
125        };
126        for validator in &self.validators {
127            validator.validate_async(_db,value).await?;
128        }
129        Ok(())
130    }
131
132    fn as_any(&self) -> &dyn Any {
133        self
134    }
135}
136
137
138/// Validates complete forms/objects with field-level rules
139///
140/// Supports:
141/// - Nested field paths (e.g., "user.address.street")
142/// - Early termination on first error
143/// - Async validation with MongoDB
144///
145/// # Example
146///
147/// ```
148/// use validate_ro::{FormValidator};
149/// use validate_ro::rules::Rule;
150///
151/// let validator = FormValidator::new()
152///     .add("username", Rule::required())
153///     .add("profile.age", Rule::integer());
154/// ```
155pub struct FormValidator {
156    break_on_error:bool,
157    field_validators: HashMap<String, Box<dyn Validator+ Send + Sync>>,
158}
159
160impl FormValidator {
161    /// Creates a new validator that collects all errors
162    pub fn new() -> Self {
163        Self {
164            break_on_error:false,
165            field_validators: HashMap::new(),
166        }
167    }
168
169    /// Adds validation rules for a field
170    ///
171    /// # Arguments
172    ///
173    /// * `field_name` - Field path (supports dot notation for nested fields)
174    /// * `validator` - Validation rules
175    pub fn add(
176        mut self,
177        field_name: &str,
178        validator: impl Validator + 'static,
179    ) -> Self {
180        self.field_validators
181            .insert(field_name.to_string(), Box::new(validator));
182        self
183    }
184
185    /// Validates form data synchronously
186    ///
187    /// Returns either:
188    /// - Ok(Document) with validated values (including defaults)
189    /// - Err(HashMap) with field names and error lists
190    pub fn validate(
191        &self,
192        form_data: &Value,
193    ) -> Result<Document, HashMap<String,Vec<ValidationError>>> {
194        let mut errors = HashMap::new();
195        let mut valid_data = HashMap::new();
196
197        for (field_name, validator) in &self.field_validators {
198            let value = if field_name.contains('.') {
199                let mut current = form_data;
200                for part in field_name.split('.') {
201                    current = current.get(part).unwrap_or(&Value::Null);
202                }
203                current
204            } else {
205                form_data.get(field_name).unwrap_or(&Value::Null)
206            };
207            let processed_value = if let Some(rules) = validator.as_any().downcast_ref::<Rules>() {
208                if value.is_null() && rules.default_value.is_some() {
209                    rules.default_value.as_ref().unwrap()
210                } else {
211                    value
212                }
213            } else {
214                value
215            };
216            if let Err(err) = validator.validate(processed_value) {
217                match errors.get_mut(field_name){
218                    None => {
219                        errors.insert(field_name.clone(),vec![err]);
220                    }
221                    Some(a) => {
222                        a.push(err);
223                    }
224                }
225
226                if self.break_on_error {
227                    break;
228                }
229            } else {
230                valid_data.insert(field_name.clone(), processed_value.clone());
231            }
232        }
233
234        if errors.is_empty() {
235            match hashmap_to_document(valid_data){
236                Ok(a) => {
237                    Ok(a)
238                }
239                Err(e) => {
240                    errors.insert("data".to_string(),vec![ValidationError::Custom(e.to_string())]);
241                    Err(errors)
242                }
243            }
244        } else {
245            Err(errors)
246        }
247    }
248
249    /// Validates form data asynchronously with MongoDB access
250    ///
251    /// Used for validators that require database checks (like uniqueness)
252    /// Returns either:
253    /// - Ok(Document) with validated values (including defaults)
254    /// - Err(HashMap) with field names and error lists
255    pub async fn validate_async(
256        &self,
257        db:&Database,
258        form_data: &Value,
259    ) -> Result<Document, HashMap<String,Vec<ValidationError>>> {
260        let mut errors = HashMap::new();
261        let mut valid_data = HashMap::new();
262
263        for (field_name, validator) in &self.field_validators {
264            let value = if field_name.contains('.') {
265                let mut current = form_data;
266                for part in field_name.split('.') {
267                    current = current.get(part).unwrap_or(&Value::Null);
268                }
269                current
270            } else {
271                form_data.get(field_name).unwrap_or(&Value::Null)
272            };
273            let processed_value = if let Some(rules) = validator.as_any().downcast_ref::<Rules>() {
274                if value.is_null() && rules.default_value.is_some() {
275                    rules.default_value.as_ref().unwrap()
276                } else {
277                    value
278                }
279            } else {
280                value
281            };
282
283            if let Err(err) = validator.validate_async(db,processed_value).await {
284                match errors.get_mut(field_name){
285                    None => {
286                        errors.insert(field_name.clone(),vec![err]);
287                    }
288                    Some(a) => {
289                        a.push(err);
290                    }
291                }
292                if self.break_on_error {
293                    break;
294                }
295            } else {
296                valid_data.insert(field_name.clone(), processed_value.clone());
297            }
298        }
299
300        if errors.is_empty() {
301            match hashmap_to_document(valid_data){
302                Ok(a) => {
303                    Ok(a)
304                }
305                Err(e) => {
306                    errors.insert("data".to_string(),vec![ValidationError::Custom(e.to_string())]);
307                    Err(errors)
308                }
309            }
310        } else {
311            Err(errors)
312        }
313    }
314
315    pub fn break_on_error(mut self) -> FormValidator {
316        self.break_on_error = true;
317        self
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}