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