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