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}