use crate::{
forms::{OutputData, OutputType, Widget},
store::{
FormCache, DB_MAP_CLIENT_NAMES, FORM_CACHE, REGEX_IS_COLOR_CODE, REGEX_IS_DATE,
REGEX_IS_DATETIME, REGEX_IS_PASSWORD,
},
};
use rand::Rng;
#[derive(serde::Deserialize, Clone, Debug)]
pub struct Meta {
pub model_name: String,
pub service_name: String,
pub database_name: String,
pub db_client_name: String,
pub collection_name: String,
pub fields_count: usize,
pub fields_name: Vec<String>,
pub is_add_docs: bool,
pub is_up_docs: bool,
pub is_del_docs: bool,
pub map_field_type: std::collections::HashMap<String, String>,
pub map_widget_type: std::collections::HashMap<String, String>,
pub map_default_values: std::collections::HashMap<String, (String, String)>,
pub map_related_models:
std::collections::HashMap<String, std::collections::HashMap<String, String>>,
pub ignore_fields: Vec<String>,
}
impl Default for Meta {
fn default() -> Self {
Meta {
model_name: String::new(),
service_name: String::new(),
database_name: String::new(),
db_client_name: String::new(),
collection_name: String::new(),
fields_count: 0_usize,
fields_name: Vec::new(),
is_add_docs: true,
is_up_docs: true,
is_del_docs: true,
map_field_type: std::collections::HashMap::new(),
map_widget_type: std::collections::HashMap::new(),
map_default_values: std::collections::HashMap::new(),
map_related_models: std::collections::HashMap::new(),
ignore_fields: Vec::new(),
}
}
}
pub trait ToModel {
fn get_hash(&self) -> Option<String>;
fn set_hash(&mut self, value: String);
fn self_to_json(&self) -> Result<serde_json::value::Value, Box<dyn std::error::Error>>;
fn key_store() -> Result<String, Box<dyn std::error::Error>>;
fn meta() -> Result<Meta, Box<dyn std::error::Error>>;
fn widgets() -> Result<std::collections::HashMap<String, Widget>, Box<dyn std::error::Error>>;
fn medium_add_validation<'a>(
&self,
) -> Result<std::collections::HashMap<&'a str, &'a str>, Box<dyn std::error::Error>>;
fn form_wig() -> Result<std::collections::HashMap<String, Widget>, Box<dyn std::error::Error>> {
let key: String = Self::key_store()?;
let mut form_store: std::sync::MutexGuard<
'_,
std::collections::HashMap<String, FormCache>,
> = FORM_CACHE.lock().unwrap();
let mut form_cache: Option<&FormCache> = form_store.get(&key[..]);
if form_cache.is_none() {
let meta: Meta = Self::meta()?;
let map_widgets: std::collections::HashMap<String, Widget> = Self::widgets()?;
let new_form_cache = FormCache {
meta,
map_widgets,
..Default::default()
};
form_store.insert(key.clone(), new_form_cache);
form_cache = form_store.get(&key[..]);
}
Ok(form_cache.unwrap().map_widgets.clone())
}
fn form_json() -> Result<String, Box<dyn std::error::Error>> {
let key: String = Self::key_store()?;
let mut form_store: std::sync::MutexGuard<
'_,
std::collections::HashMap<String, FormCache>,
> = FORM_CACHE.lock().unwrap();
let mut form_cache: Option<&FormCache> = form_store.get(&key[..]);
if form_cache.is_none() {
let meta: Meta = Self::meta()?;
let map_widgets: std::collections::HashMap<String, Widget> = Self::widgets()?;
let new_form_cache = FormCache {
meta,
map_widgets,
..Default::default()
};
form_store.insert(key.clone(), new_form_cache);
form_cache = form_store.get(&key[..]);
}
let form_cache: &FormCache = form_cache.unwrap();
if form_cache.attrs_json.is_empty() {
let map_widgets: std::collections::HashMap<String, Widget> =
form_cache.map_widgets.clone();
let mut json_text = String::new();
for (field_name, widget) in map_widgets {
let tmp: String = serde_json::to_string(&widget)?;
if !json_text.is_empty() {
json_text = format!("{},\"{}\":{}", json_text, field_name, tmp);
} else {
json_text = format!("\"{}\":{}", field_name, tmp);
}
}
let mut form_cache: FormCache = form_cache.clone();
form_cache.attrs_json = format!("{{{}}}", json_text);
form_store.insert(key, form_cache.clone());
return Ok(form_cache.attrs_json);
}
Ok(form_cache.attrs_json.clone())
}
fn form_html() -> Result<String, Box<dyn std::error::Error>> {
let key: String = Self::key_store()?;
let mut form_store: std::sync::MutexGuard<
'_,
std::collections::HashMap<String, FormCache>,
> = FORM_CACHE.lock().unwrap();
let mut form_cache: Option<&FormCache> = form_store.get(&key[..]);
if form_cache.is_none() {
let meta: Meta = Self::meta()?;
let map_widgets: std::collections::HashMap<String, Widget> = Self::widgets()?;
let new_form_cache = FormCache {
meta,
map_widgets,
..Default::default()
};
form_store.insert(key.clone(), new_form_cache);
form_cache = form_store.get(&key[..]);
}
let form_cache: &FormCache = form_cache.unwrap();
if form_cache.controls_html.is_empty() {
let map_widgets: std::collections::HashMap<String, Widget> =
form_cache.map_widgets.clone();
let controls: String = Self::medium_to_html(&form_cache.meta.fields_name, map_widgets)?;
let mut form_cache: FormCache = form_cache.clone();
form_cache.controls_html = controls.clone();
form_store.insert(key, form_cache.clone());
return Ok(controls);
}
Ok(form_cache.controls_html.clone())
}
fn check_minlength(minlength: usize, value: &str) -> Result<(), Box<dyn std::error::Error>> {
if minlength > 0 && value.encode_utf16().count() < minlength {
Err(format!("Exceeds limit, minlength={}.", minlength))?
}
Ok(())
}
fn check_maxlength(maxlength: usize, value: &str) -> Result<(), Box<dyn std::error::Error>> {
if maxlength > 0 && value.encode_utf16().count() > maxlength {
Err(format!("Exceeds limit, maxlength={}.", maxlength))?
}
Ok(())
}
fn accumula_err(widget: &Widget, err: &String) -> Result<String, Box<dyn std::error::Error>> {
let mut tmp = widget.error.clone();
tmp = if !tmp.is_empty() {
format!("{}<br>", tmp)
} else {
String::new()
};
Ok(format!("{}{}", tmp, err))
}
fn regex_validation(field_type: &str, value: &str) -> Result<(), Box<dyn std::error::Error>> {
match field_type {
"inputEmail" => {
if !validator::validate_email(value) {
Err("Invalid email address.")?
}
}
"inputColor" => {
if !REGEX_IS_COLOR_CODE.is_match(value) {
Err("Invalid Color code.")?
}
}
"inputUrl" => {
if !validator::validate_url(value) {
Err("Invalid Url.")?
}
}
"inputIP" => {
if !validator::validate_ip(value) {
Err("Invalid IP address.")?
}
}
"inputIPv4" => {
if !validator::validate_ip_v4(value) {
Err("Invalid IPv4 address.")?
}
}
"inputIPv6" => {
if !validator::validate_ip_v6(value) {
Err("Invalid IPv6 address.")?
}
}
"inputPassword" => {
if !REGEX_IS_PASSWORD.is_match(value) {
Err(
"Allowed characters: a-z A-Z 0-9 @ # $ % ^ & + = * ! ~ ) (<br> \
Minimum size 8 characters",
)?
}
}
"inputDate" => {
if !REGEX_IS_DATE.is_match(value) {
Err("Incorrect date format.<br>Example: 1970-02-28")?
}
}
"inputDateTime" => {
if !REGEX_IS_DATETIME.is_match(value) {
Err("Incorrect date and time format.<br>Example: 1970-02-28T00:00")?
}
}
_ => return Ok(()),
}
Ok(())
}
fn check_unique(
hash: &str,
field_name: &str,
bson_field_value: &mongodb::bson::Bson,
coll: &mongodb::sync::Collection,
) -> Result<(), Box<dyn std::error::Error>> {
let object_id = mongodb::bson::oid::ObjectId::with_string(hash);
let mut filter = mongodb::bson::doc! { field_name.to_string() : bson_field_value };
if let Ok(id) = object_id {
filter = mongodb::bson::doc! {
"$and".to_string() : [
{ "_id" : { "$ne".to_string() : id } },
filter
]
};
}
let count: i64 = coll.count_documents(filter, None)?;
if count > 0 {
Err("Is not unique.")?
}
Ok(())
}
fn create_password_hash(field_value: &str) -> Result<String, Box<dyn std::error::Error>> {
const CHARSET: &[u8] =
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@#$%^&+=*!~)(";
const SALT_LEN: usize = 12;
let mut rng = rand::thread_rng();
let password: &[u8] = field_value.as_bytes();
let salt: String = (0..SALT_LEN)
.map(|_| {
let idx = rng.gen_range(0, CHARSET.len());
CHARSET[idx] as char
})
.collect();
let salt: &[u8] = salt.as_bytes();
let config = argon2::Config::default();
let hash: String = argon2::hash_encoded(password, salt, &config)?;
Ok(hash)
}
fn to_hash(
map_widgets: &std::collections::HashMap<String, Widget>,
) -> Result<String, Box<dyn std::error::Error>> {
let mut errors = String::new();
for (field_name, widget) in map_widgets {
let tmp = if !errors.is_empty() {
format!("{} ; ", errors)
} else {
String::new()
};
if !widget.error.is_empty() {
errors = format!("{}Field: `{}` - {}", tmp, field_name, widget.error);
}
}
if !errors.is_empty() {
Err(errors.replace("<br>", " | "))?
}
Ok(map_widgets.get(&"hash".to_owned()).unwrap().value.clone())
}
fn to_json(
map_widgets: &std::collections::HashMap<String, Widget>,
) -> Result<String, Box<dyn std::error::Error>> {
let mut json_text = String::new();
for (field_name, widget) in map_widgets {
let widget_json = serde_json::to_string(&widget).unwrap();
if !json_text.is_empty() {
json_text = format!("{},\"{}\":{}", json_text, field_name, widget_json);
} else {
json_text = format!("\"{}\":{}", field_name, widget_json);
}
}
Ok(format!("{{{}}}", json_text))
}
fn medium_to_html(
fields_name: &Vec<String>,
map_widgets: std::collections::HashMap<String, Widget>,
) -> Result<String, Box<dyn std::error::Error>>;
fn check(&self, output_format: OutputType) -> Result<OutputData, Box<dyn std::error::Error>> {
let key: String = Self::key_store()?;
let mut form_store: std::sync::MutexGuard<
'_,
std::collections::HashMap<String, FormCache>,
> = FORM_CACHE.lock()?;
let mut form_cache: Option<&FormCache> = form_store.get(&key[..]);
if form_cache.is_none() {
let meta: Meta = Self::meta()?;
let map_widgets: std::collections::HashMap<String, Widget> = Self::widgets()?;
let new_form_cache = FormCache {
meta,
map_widgets,
..Default::default()
};
form_store.insert(key.clone(), new_form_cache);
form_cache = form_store.get(&key[..]);
}
let form_cache: &FormCache = form_cache.unwrap();
let meta: &Meta = &form_cache.meta;
let client_store: std::sync::MutexGuard<
'_,
std::collections::HashMap<String, mongodb::sync::Client>,
> = DB_MAP_CLIENT_NAMES.lock()?;
let client_cache: &mongodb::sync::Client = client_store.get(&meta.db_client_name).unwrap();
let model_name: &str = meta.model_name.as_str();
let mut err_symptom = false;
let hash = self.get_hash().unwrap_or_default();
let hash = hash.as_str();
let is_update: bool = !hash.is_empty();
let ignore_fields: Vec<&str> = meta
.ignore_fields
.iter()
.map(|item| item.as_str())
.collect();
let coll: mongodb::sync::Collection = client_cache
.database(&meta.database_name)
.collection(&meta.collection_name);
let pre_json: serde_json::value::Value = self.self_to_json()?;
let mut final_doc = mongodb::bson::document::Document::new();
let fields_name: Vec<&str> = meta.fields_name.iter().map(|item| item.as_str()).collect();
let mut map_widgets: std::collections::HashMap<String, Widget> =
form_cache.map_widgets.clone();
{
let error_map = self.medium_add_validation()?;
if !error_map.is_empty() {
err_symptom = true;
for (field_name, err_msg) in error_map {
if !fields_name.contains(&field_name) {
Err(format!(
"Model: `{}` > Method: `add_validation()` : \
The `{}` field is missing from the model.",
model_name, field_name
))?
}
if let Some(widget) = map_widgets.get_mut(&field_name.to_owned()) {
widget.error = Self::accumula_err(&widget, &err_msg.to_string())?;
}
}
}
}
for field_name in fields_name {
if field_name == "hash" {
continue;
}
let pre_json_value: Option<&serde_json::value::Value> = pre_json.get(field_name);
if pre_json_value.is_none() {
Err(format!(
"Model: `{}` > Field: `{}` > Method: `check()` : This field is missing.",
model_name, field_name
))?
}
let pre_json_value: &serde_json::value::Value = pre_json_value.unwrap();
let widget: &mut Widget = map_widgets.get_mut(field_name).unwrap();
let widget_type: &str = &widget.widget.clone()[..];
match widget_type {
"checkBoxText" | "radioText" | "inputColor" | "inputEmail" | "inputPassword"
| "inputPhone" | "inputText" | "inputUrl" | "inputIP" | "inputIPv4"
| "inputIPv6" | "textArea" | "selectText" => {
let mut field_value: String = if !pre_json_value.is_null() {
let clean_data: String =
pre_json_value.as_str().unwrap().trim().to_string();
if widget_type != "inputPassword" {
widget.value = clean_data.clone();
} else {
widget.value = String::new();
}
clean_data
} else {
String::new()
};
if field_value.is_empty() {
if widget.required {
err_symptom = true;
widget.error =
Self::accumula_err(&widget, &"Required field.".to_owned()).unwrap();
widget.value = String::new();
continue;
} else {
if !is_update && widget_type != "inputPassword" {
if !widget.value.is_empty() {
field_value = widget.value.trim().to_string();
widget.value = String::new();
} else if !err_symptom && !ignore_fields.contains(&field_name) {
final_doc
.insert(field_name.to_string(), mongodb::bson::Bson::Null);
widget.value = String::new();
continue;
} else {
widget.value = String::new();
continue;
}
} else {
widget.value = String::new();
continue;
}
}
}
let bson_field_value = if widget_type != "inputPassword" {
mongodb::bson::Bson::String(field_value.clone())
} else {
mongodb::bson::Bson::Null
};
let field_value: &str = field_value.as_str();
Self::check_minlength(widget.minlength, field_value).unwrap_or_else(|err| {
err_symptom = true;
widget.error = Self::accumula_err(&widget, &err.to_string()).unwrap();
});
Self::check_maxlength(widget.maxlength, field_value).unwrap_or_else(|err| {
err_symptom = true;
widget.error = Self::accumula_err(&widget, &err.to_string()).unwrap();
});
let min: f64 = widget.minlength.clone() as f64;
let max: f64 = widget.maxlength.clone() as f64;
let len: f64 = field_value.encode_utf16().count() as f64;
if (min > 0_f64 || max > 0_f64)
&& !validator::validate_range(
validator::Validator::Range {
min: Some(min),
max: Some(max),
},
len,
)
{
err_symptom = true;
let msg = format!(
"Length {} is out of range (min={} <> max={}).",
len, min, max
);
widget.error = Self::accumula_err(&widget, &msg).unwrap();
}
if widget_type != "inputPassword" && widget.unique {
Self::check_unique(hash, field_name, &bson_field_value, &coll)
.unwrap_or_else(|err| {
err_symptom = true;
widget.error =
Self::accumula_err(&widget, &err.to_string()).unwrap();
});
}
Self::regex_validation(widget_type, field_value).unwrap_or_else(|err| {
err_symptom = true;
widget.error = Self::accumula_err(&widget, &err.to_string()).unwrap();
});
if !err_symptom && !ignore_fields.contains(&field_name) {
match widget_type {
"inputPassword" => {
if !is_update && !field_value.is_empty() {
let hash: String = Self::create_password_hash(field_value)?;
final_doc.insert(
field_name.to_string(),
mongodb::bson::Bson::String(hash),
);
}
}
_ => {
final_doc.insert(field_name.to_string(), bson_field_value);
}
}
}
}
"inputDate" | "inputDateTime" => {
let mut field_value: String = if pre_json_value.is_null() {
let clean_data: String =
pre_json_value.as_str().unwrap().trim().to_string();
widget.value = clean_data.clone();
clean_data
} else {
String::new()
};
if field_value.is_empty() {
if widget.required {
err_symptom = true;
widget.error =
Self::accumula_err(&widget, &"Required field.".to_owned()).unwrap();
widget.value = String::new();
continue;
} else {
if !is_update {
if !widget.value.is_empty() {
field_value = widget.value.trim().to_string();
widget.value = String::new();
} else if !err_symptom && !ignore_fields.contains(&field_name) {
final_doc
.insert(field_name.to_string(), mongodb::bson::Bson::Null);
widget.value = String::new();
continue;
} else {
widget.value = String::new();
continue;
}
} else {
widget.value = String::new();
continue;
}
}
}
let field_value: &str = field_value.as_str();
Self::regex_validation(widget_type, field_value).unwrap_or_else(|err| {
err_symptom = true;
widget.error = Self::accumula_err(&widget, &err.to_string()).unwrap();
});
if err_symptom {
continue;
}
let dt_value: chrono::DateTime<chrono::Utc> = {
let field_value: String = if widget_type == "inputDate" {
format!("{}T00:00", field_value.to_string())
} else {
field_value.to_string()
};
chrono::DateTime::<chrono::Utc>::from_utc(
chrono::NaiveDateTime::parse_from_str(&field_value, "%Y-%m-%dT%H:%M")?,
chrono::Utc,
)
};
if !widget.min.is_empty() && !widget.max.is_empty() {
let dt_min: chrono::DateTime<chrono::Utc> = {
let min_value: String = if widget_type == "inputDate" {
format!("{}T00:00", widget.min.clone())
} else {
widget.min.clone()
};
chrono::DateTime::<chrono::Utc>::from_utc(
chrono::NaiveDateTime::parse_from_str(
&min_value,
"%Y-%m-%dT%H:%M",
)?,
chrono::Utc,
)
};
let dt_max: chrono::DateTime<chrono::Utc> = {
let max_value: String = if widget_type == "inputDate" {
format!("{}T00:00", widget.max.clone())
} else {
widget.max.clone()
};
chrono::DateTime::<chrono::Utc>::from_utc(
chrono::NaiveDateTime::parse_from_str(
&max_value,
"%Y-%m-%dT%H:%M",
)?,
chrono::Utc,
)
};
if dt_value < dt_min || dt_value > dt_max {
err_symptom = true;
widget.error = Self::accumula_err(
&widget,
&"Date out of range between `min` and` max`.".to_owned(),
)
.unwrap();
continue;
}
}
let dt_value_bson = mongodb::bson::Bson::DateTime(dt_value);
if widget.unique {
Self::check_unique(hash, field_name, &dt_value_bson, &coll).unwrap_or_else(
|err| {
err_symptom = true;
widget.error =
Self::accumula_err(&widget, &err.to_string()).unwrap();
},
);
}
if !err_symptom && !ignore_fields.contains(&field_name) {
final_doc.insert(field_name.to_string(), dt_value_bson);
}
}
"checkBoxI32" | "radioI32" | "numberI32" | "rangeI32" | "selectI32" => {
let mut field_value: Option<i64> = pre_json_value.as_i64();
let is_null_value: bool = pre_json_value.is_null();
if is_null_value {
if widget.required {
err_symptom = true;
widget.error =
Self::accumula_err(&widget, &"Required field.".to_owned()).unwrap();
widget.value = String::new();
continue;
} else if is_null_value {
if !is_update {
if !widget.value.is_empty() {
field_value = Some(widget.value.trim().parse::<i64>().unwrap());
widget.value = String::new();
} else if !err_symptom && !ignore_fields.contains(&field_name) {
final_doc
.insert(field_name.to_string(), mongodb::bson::Bson::Null);
widget.value = String::new();
continue;
} else {
widget.value = String::new();
continue;
}
} else {
widget.value = String::new();
continue;
}
}
}
let field_value: i32 = field_value.unwrap() as i32;
if !is_null_value {
widget.value = field_value.to_string();
}
let bson_field_value = mongodb::bson::Bson::Int32(field_value);
if widget.unique {
Self::check_unique(hash, field_name, &bson_field_value, &coll)
.unwrap_or_else(|err| {
err_symptom = true;
widget.error =
Self::accumula_err(&widget, &err.to_string()).unwrap();
});
}
let min: f64 = widget.min.parse().unwrap();
let max: f64 = widget.max.parse().unwrap();
let num: f64 = field_value as f64;
if (min > 0_f64 || max > 0_f64)
&& !validator::validate_range(
validator::Validator::Range {
min: Some(min),
max: Some(max),
},
num,
)
{
err_symptom = true;
let msg = format!(
"Number {} is out of range (min={} <> max={}).",
num, min, max
);
widget.error = Self::accumula_err(&widget, &msg).unwrap();
}
if !err_symptom && !ignore_fields.contains(&field_name) {
final_doc.insert(field_name.to_string(), bson_field_value);
}
}
"checkBoxU32" | "radioU32" | "numberU32" | "rangeU32" | "selectU32"
| "checkBoxI64" | "radioI64" | "numberI64" | "rangeI64" | "selectI64" => {
let mut field_value: Option<i64> = pre_json_value.as_i64();
let is_null_value: bool = pre_json_value.is_null();
if is_null_value {
if widget.required {
err_symptom = true;
widget.error =
Self::accumula_err(&widget, &"Required field.".to_owned()).unwrap();
widget.value = String::new();
continue;
} else if is_null_value {
if !is_update {
if !widget.value.is_empty() {
field_value = Some(widget.value.trim().parse::<i64>().unwrap());
widget.value = String::new();
} else if !err_symptom && !ignore_fields.contains(&field_name) {
final_doc
.insert(field_name.to_string(), mongodb::bson::Bson::Null);
widget.value = String::new();
continue;
} else {
widget.value = String::new();
continue;
}
} else {
widget.value = String::new();
continue;
}
}
}
let field_value: i64 = field_value.unwrap();
if !is_null_value {
widget.value = field_value.to_string();
}
let bson_field_value = mongodb::bson::Bson::Int64(field_value);
if widget.unique {
Self::check_unique(hash, field_name, &bson_field_value, &coll)
.unwrap_or_else(|err| {
err_symptom = true;
widget.error =
Self::accumula_err(&widget, &err.to_string()).unwrap();
});
}
let min: f64 = widget.min.parse().unwrap();
let max: f64 = widget.max.parse().unwrap();
let num: f64 = field_value as f64;
if (min > 0_f64 || max > 0_f64)
&& !validator::validate_range(
validator::Validator::Range {
min: Some(min),
max: Some(max),
},
num,
)
{
err_symptom = true;
let msg = format!(
"Number {} is out of range (min={} <> max={}).",
num, min, max
);
widget.error = Self::accumula_err(&widget, &msg).unwrap();
}
if !err_symptom && !ignore_fields.contains(&field_name) {
final_doc.insert(field_name.to_string(), bson_field_value);
}
}
"checkBoxF64" | "radioF64" | "numberF64" | "rangeF64" | "selectF64" => {
let mut field_value: Option<f64> = pre_json_value.as_f64();
let is_null_value: bool = pre_json_value.is_null();
if is_null_value {
if widget.required {
err_symptom = true;
widget.error =
Self::accumula_err(&widget, &"Required field.".to_owned()).unwrap();
widget.value = String::new();
continue;
} else if is_null_value {
if !is_update {
if !widget.value.is_empty() {
field_value = Some(widget.value.trim().parse::<f64>().unwrap());
widget.value = String::new();
} else if !err_symptom && !ignore_fields.contains(&field_name) {
final_doc
.insert(field_name.to_string(), mongodb::bson::Bson::Null);
widget.value = String::new();
continue;
} else {
widget.value = String::new();
continue;
}
} else {
widget.value = String::new();
continue;
}
}
}
let field_value: f64 = field_value.unwrap();
if !is_null_value {
widget.value = field_value.to_string();
}
let bson_field_value = mongodb::bson::Bson::Double(field_value);
if widget.unique {
Self::check_unique(hash, field_name, &bson_field_value, &coll)
.unwrap_or_else(|err| {
err_symptom = true;
widget.error =
Self::accumula_err(&widget, &err.to_string()).unwrap();
});
}
let min: f64 = widget.min.parse().unwrap();
let max: f64 = widget.max.parse().unwrap();
let num: f64 = field_value.clone();
if (min > 0_f64 || max > 0_f64)
&& !validator::validate_range(
validator::Validator::Range {
min: Some(min),
max: Some(max),
},
num,
)
{
err_symptom = true;
let msg = format!(
"Number {} is out of range (min={} <> max={}).",
num, min, max
);
widget.error = Self::accumula_err(&widget, &msg).unwrap();
}
if !err_symptom && !ignore_fields.contains(&field_name) {
final_doc.insert(field_name.to_string(), bson_field_value);
}
}
"checkBoxBool" => {
let field_value: bool = if pre_json_value.is_null() {
false
} else {
true
};
widget.checked = field_value.clone();
let bson_field_value = mongodb::bson::Bson::Boolean(field_value);
if widget.unique {
Self::check_unique(hash, field_name, &bson_field_value, &coll)
.unwrap_or_else(|err| {
err_symptom = true;
widget.error =
Self::accumula_err(&widget, &err.to_string()).unwrap();
});
}
if !err_symptom && !ignore_fields.contains(&field_name) {
final_doc.insert(field_name.to_string(), bson_field_value);
}
}
_ => Err(format!(
"Model: `{}` > Field: `{}` > Method: \
`check()` : Unsupported data type.",
model_name, field_name
))?,
}
if !err_symptom {
let dt: chrono::DateTime<chrono::Utc> = chrono::Utc::now();
if !is_update {
final_doc.insert("created_at".to_string(), mongodb::bson::Bson::DateTime(dt));
final_doc.insert("updated_at".to_string(), mongodb::bson::Bson::DateTime(dt));
} else {
final_doc.insert("updated_at".to_string(), mongodb::bson::Bson::DateTime(dt));
}
}
}
let result: OutputData = match output_format {
OutputType::Hash => {
let data: String = Self::to_hash(&map_widgets)?;
OutputData::Hash((data, !err_symptom, final_doc))
}
OutputType::Wig => OutputData::Wig((map_widgets, !err_symptom, final_doc)),
OutputType::Json => {
let data: String = Self::to_json(&map_widgets)?;
OutputData::Json((data, !err_symptom, final_doc))
}
OutputType::Html => {
let data: String = Self::medium_to_html(&meta.fields_name, map_widgets)?;
OutputData::Html((data, !err_symptom, final_doc))
}
};
Ok(result)
}
fn save(
&mut self,
output_format: OutputType,
) -> Result<OutputData, Box<dyn std::error::Error>> {
let verified_data: OutputData = self.check(OutputType::Wig)?;
let key: String = Self::key_store()?;
let form_store: std::sync::MutexGuard<'_, std::collections::HashMap<String, FormCache>> =
FORM_CACHE.lock().unwrap();
let form_cache: Option<&FormCache> = form_store.get(&key[..]);
let form_cache: &FormCache = form_cache.unwrap();
let meta: &Meta = &form_cache.meta;
let client_store: std::sync::MutexGuard<
'_,
std::collections::HashMap<String, mongodb::sync::Client>,
> = DB_MAP_CLIENT_NAMES.lock().unwrap();
let client_cache: &mongodb::sync::Client = client_store.get(&meta.db_client_name).unwrap();
let mut map_widgets: std::collections::HashMap<String, Widget> = verified_data.wig();
let is_update: bool = !self.get_hash().unwrap_or_default().is_empty();
let coll: mongodb::sync::Collection = client_cache
.database(&meta.database_name)
.collection(&meta.collection_name);
if verified_data.bool() {
let final_doc = verified_data.doc();
if !is_update {
let result: mongodb::results::InsertOneResult = coll.insert_one(final_doc, None)?;
self.set_hash(result.inserted_id.as_object_id().unwrap().to_hex());
} else if !final_doc.is_empty() {
let object_id: mongodb::bson::oid::ObjectId =
mongodb::bson::oid::ObjectId::with_string(
self.get_hash().unwrap_or_default().as_str(),
)?;
let query: mongodb::bson::document::Document =
mongodb::bson::doc! {"_id": object_id};
let update: mongodb::bson::document::Document = mongodb::bson::doc! {
"$set".to_string() :
mongodb::bson::Bson::Document(final_doc),
};
coll.update_one(query, update, None)?;
}
}
if self.get_hash().is_some() {
map_widgets.get_mut(&"hash".to_owned()).unwrap().value =
self.get_hash().unwrap_or_default();
}
let result: OutputData = match output_format {
OutputType::Hash => {
let data: String = Self::to_hash(&map_widgets)?;
OutputData::Hash((data, verified_data.bool(), verified_data.doc()))
}
OutputType::Wig => {
OutputData::Wig((map_widgets, verified_data.bool(), verified_data.doc()))
}
OutputType::Json => {
let data: String = Self::to_json(&map_widgets)?;
OutputData::Json((data, verified_data.bool(), verified_data.doc()))
}
OutputType::Html => {
let data: String = Self::medium_to_html(&meta.fields_name, map_widgets)?;
OutputData::Html((data, verified_data.bool(), verified_data.doc()))
}
};
Ok(result)
}
}
pub trait AdditionalValidation {
fn add_validation<'a>(
&self,
) -> Result<std::collections::HashMap<&'a str, &'a str>, Box<dyn std::error::Error>> {
let error_map: std::collections::HashMap<&'a str, &'a str> =
std::collections::HashMap::new();
Ok(error_map)
}
}