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