use std::any::Any;
use std::collections::HashMap;
use async_trait::async_trait;
use mongodb::bson::{to_bson, Document};
use mongodb::{bson, Database};
use serde_json::Value;
use crate::error::ValidationError;
use crate::traits::{ValidationResult, Validator};
pub mod rules;
pub mod traits;
pub mod error;
mod macros;
pub use macros::*;
pub struct Rules {
validators: Vec<Box<dyn Validator+ Send + Sync>>,
default_value: Option<Value>,
}
impl Rules {
pub fn new() -> Self {
Self {
validators: Vec::new(),
default_value: None,
}
}
pub fn add<V: Validator + 'static>(mut self, validator: V) -> Self {
self.validators.push(Box::new(validator));
self
}
pub fn default(mut self, default: Value) -> Self {
self.default_value = Some(default);
self
}
pub fn len(&self) -> usize {
self.validators.len()
}
}
#[async_trait]
impl Validator for Rules {
fn validate(&self, value: &Value) -> ValidationResult {
let value = if value.is_null() && self.default_value.is_some() {
self.default_value.as_ref().unwrap()
} else {
value
};
for validator in &self.validators {
validator.validate(value)?;
}
Ok(())
}
async fn validate_async(&self, _db: &Database, value: &Value) -> ValidationResult {
let value = if value.is_null() && self.default_value.is_some() {
self.default_value.as_ref().unwrap()
} else {
value
};
for validator in &self.validators {
validator.validate_async(_db,value).await?;
}
Ok(())
}
fn as_any(&self) -> &dyn Any {
self
}
}
pub struct FormValidator {
break_on_error:bool,
field_validators: HashMap<String, Box<dyn Validator+ Send + Sync>>,
}
impl FormValidator {
pub fn new() -> Self {
Self {
break_on_error:false,
field_validators: HashMap::new(),
}
}
pub fn add(
mut self,
field_name: &str,
validator: impl Validator + 'static,
) -> Self {
self.field_validators
.insert(field_name.to_string(), Box::new(validator));
self
}
pub fn validate(
&self,
form_data: &Value,
) -> Result<Document, HashMap<String,Vec<ValidationError>>> {
let mut errors = HashMap::new();
let mut valid_data = HashMap::new();
for (field_name, validator) in &self.field_validators {
let value = if field_name.contains('.') {
let mut current = form_data;
for part in field_name.split('.') {
current = current.get(part).unwrap_or(&Value::Null);
}
current
} else {
form_data.get(field_name).unwrap_or(&Value::Null)
};
let processed_value = if let Some(rules) = validator.as_any().downcast_ref::<Rules>() {
if value.is_null() && rules.default_value.is_some() {
rules.default_value.as_ref().unwrap()
} else {
value
}
} else {
value
};
if let Err(err) = validator.validate(processed_value) {
match errors.get_mut(field_name){
None => {
errors.insert(field_name.clone(),vec![err]);
}
Some(a) => {
a.push(err);
}
}
if self.break_on_error {
break;
}
} else {
valid_data.insert(field_name.clone(), processed_value.clone());
}
}
if errors.is_empty() {
match hashmap_to_document(valid_data){
Ok(a) => {
Ok(a)
}
Err(e) => {
errors.insert("data".to_string(),vec![ValidationError::Custom(e.to_string())]);
Err(errors)
}
}
} else {
Err(errors)
}
}
pub async fn validate_async(
&self,
db:&Database,
form_data: &Value,
) -> Result<Document, HashMap<String,Vec<ValidationError>>> {
let mut errors = HashMap::new();
let mut valid_data = HashMap::new();
for (field_name, validator) in &self.field_validators {
let value = if field_name.contains('.') {
let mut current = form_data;
for part in field_name.split('.') {
current = current.get(part).unwrap_or(&Value::Null);
}
current
} else {
form_data.get(field_name).unwrap_or(&Value::Null)
};
let processed_value = if let Some(rules) = validator.as_any().downcast_ref::<Rules>() {
if value.is_null() && rules.default_value.is_some() {
rules.default_value.as_ref().unwrap()
} else {
value
}
} else {
value
};
if let Err(err) = validator.validate_async(db,processed_value).await {
match errors.get_mut(field_name){
None => {
errors.insert(field_name.clone(),vec![err]);
}
Some(a) => {
a.push(err);
}
}
if self.break_on_error {
break;
}
} else {
valid_data.insert(field_name.clone(), processed_value.clone());
}
}
if errors.is_empty() {
match hashmap_to_document(valid_data){
Ok(a) => {
Ok(a)
}
Err(e) => {
errors.insert("data".to_string(),vec![ValidationError::Custom(e.to_string())]);
Err(errors)
}
}
} else {
Err(errors)
}
}
pub fn break_on_error(mut self) -> FormValidator {
self.break_on_error = true;
self
}
}
fn hashmap_to_document(input: HashMap<String, Value>) -> Result<Document, bson::ser::Error> {
let mut doc = Document::new();
for (key, value) in input {
let bson_value = to_bson(&value)?;
doc.insert(key, bson_value);
}
Ok(doc)
}