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