#[macro_export]
macro_rules! model {
( struct $sname:ident { $($fname:ident : $ftype:ty),+ }
$(#[$attrs:meta])* $($impls:item)+ ) => {
#[derive(Serialize, Deserialize, Default, Clone, Debug)]
pub struct $sname {
$(pub $fname : $ftype),+
}
$(#[$attrs])*
$($impls)+
impl $sname {
pub fn model_name<'a>() -> Result<&'a str, Box<dyn std::error::Error>> {
Ok(stringify!($sname))
}
pub fn field_names<'a>() -> Result<&'a [&'a str], Box<dyn std::error::Error>> {
Ok(&[$(stringify!($fname)),*])
}
pub fn field_types<'a>() -> Result<std::collections::HashMap<&'a str, &'a str>, Box<dyn std::error::Error>> {
static FIELD_NAMES: &'static [&'static str] = &[$(stringify!($fname)),*];
Ok(FIELD_NAMES.iter().map(|item| item.to_owned())
.zip([$(stringify!($ftype)),*].iter().map(|item| item.to_owned())).collect())
}
pub fn metadata<'a>() -> Result<mango_orm::models::Meta<'a>, Box<dyn std::error::Error>> {
let mut meta: mango_orm::models::Meta = Self::meta()?;
meta.service = meta.service.to_lowercase();
meta.database = meta.database.to_lowercase();
meta.collection = format!("{}__{}",
meta.service, stringify!($sname).to_lowercase()
);
Ok(meta)
}
pub fn widgets_full_map<'a>() -> Result<std::collections::HashMap<&'a str, mango_orm::widgets::Widget>, Box<dyn std::error::Error>> {
let mut map: std::collections::HashMap<&str, mango_orm::widgets::Widget> = Self::widgets()?;
if map.get("hash").is_none() {
map.insert(
"hash",
mango_orm::widgets::Widget {
value: mango_orm::widgets::FieldType::Hash,
hidden: true,
..Default::default()
}
);
}
Ok(map)
}
pub async fn form_cache() -> Result<(
async_mutex::MutexGuard<'static, std::collections::HashMap<String,
mango_orm::store::FormCache>>, String), Box<dyn std::error::Error>> {
let meta: mango_orm::models::Meta = Self::metadata()?;
let key = meta.collection.clone();
let mut store: async_mutex::MutexGuard<'_, std::collections::HashMap<String,
mango_orm::store::FormCache>> = mango_orm::store::FORM_CACHE.lock().await;
let mut cache: Option<&mango_orm::store::FormCache> = store.get(&key);
if cache.is_none() {
let widgets: std::collections::HashMap<&str, mango_orm::widgets::Widget> = Self::widgets_full_map()?;
let mut clean_attrs: std::collections::HashMap<String, mango_orm::widgets::Transport> = std::collections::HashMap::new();
let mut widget_map: std::collections::HashMap<String, String> = std::collections::HashMap::new();
for (field, widget) in &widgets {
clean_attrs.insert(field.to_string(), widget.clean_attrs(field)?);
widget_map.insert(
field.to_string(), widget.value.get_enum_type().to_string());
}
let form_cache = mango_orm::store::FormCache{
attrs_map: clean_attrs,
widget_map: widget_map,
..Default::default()
};
store.insert(key.clone(), form_cache);
}
Ok((store, key))
}
pub async fn form_map() -> Result<std::collections::HashMap<String, mango_orm::widgets::Transport>, Box<dyn std::error::Error>> {
let (store, key) = Self::form_cache().await?;
let cache: Option<&mango_orm::store::FormCache> = store.get(&key);
if cache.is_some() {
let clean_attrs: std::collections::HashMap<String, mango_orm::widgets::Transport> = cache.unwrap().attrs_map.clone();
Ok(clean_attrs)
} else {
Err(format!("Model: `{}` -> Method: `form_map()` : \
Did not receive data from cache.",
stringify!($sname)))?
}
}
pub async fn form_json() -> Result<String, Box<dyn std::error::Error>> {
let (mut store, key) = Self::form_cache().await?;
let cache: Option<&mango_orm::store::FormCache> = store.get(&key);
if cache.is_some() {
let cache: &mango_orm::store::FormCache = cache.unwrap();
if cache.attrs_json.is_empty() {
let mut form_cache: mango_orm::store::FormCache = cache.clone();
let attrs: std::collections::HashMap<String, mango_orm::widgets::Transport> = form_cache.attrs_map.clone();
let mut json_text = String::new();
for (field, trans) in attrs {
let tmp = serde_json::to_string(&trans).unwrap();
if !json_text.is_empty() {
json_text = format!("{},\"{}\":{}", json_text, field, tmp);
} else {
json_text = format!("\"{}\":{}", field, tmp);
}
}
form_cache.attrs_json = format!("{{{}}}", json_text);
store.insert(key, form_cache.clone());
return Ok(form_cache.attrs_json);
}
Ok(cache.attrs_json.clone())
} else {
Err(format!("Model: `{}` -> Method: `form_json()` : \
Did not receive data from cache.",
stringify!($sname)))?
}
}
pub async fn form_html() ->
Result<String, Box<dyn std::error::Error>> {
let (mut store, key) = Self::form_cache().await?;
let model_name: &str = &stringify!($sname).to_lowercase();
let mut build_controls = false;
let mut attrs_map: std::collections::HashMap<String, mango_orm::widgets::Transport> = std::collections::HashMap::new();
let cache: Option<&mango_orm::store::FormCache> = store.get(&key);
if cache.is_some() {
let cache: &mango_orm::store::FormCache = cache.unwrap();
let is_cached: bool = cache.form_html.is_empty();
if is_cached {
build_controls = true;
attrs_map = cache.attrs_map.clone();
}
let controls = Self::html(
attrs_map,
model_name,
build_controls
)?;
if is_cached {
let mut form_cache: mango_orm::store::FormCache = cache.clone();
form_cache.form_html = controls.clone();
store.insert(key, form_cache.clone());
return Ok(controls);
}
Ok(cache.form_html.clone())
} else {
Err(format!("Model: `{}` -> Method: `form_html()` : \
Did not receive data from cache.",
stringify!($sname)))?
}
}
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(())
}
async fn check_unique(
is_update: bool, is_unique: bool, field_name: String, value_bson_pre: &mongodb::bson::Bson,
value_type: &str, coll: &mongodb::Collection) -> Result<(), Box<dyn std::error::Error>> {
if !is_update && is_unique {
let filter: mongodb::bson::document::Document = match value_type {
"i64" => {
let field_value: i64 = value_bson_pre.as_i64().unwrap();
mongodb::bson::doc!{ field_name.to_string() : mongodb::bson::Bson::Int64(field_value) }
}
_ => {
mongodb::bson::doc!{ field_name.to_string() : value_bson_pre }
}
};
let count: i64 = coll.count_documents(filter, None).await?;
if count > 0 {
Err("Is not unique.")?
}
}
Ok(())
}
fn accumula_err(attrs: &mango_orm::widgets::Transport, err: &String) ->
Result<String, Box<dyn std::error::Error>> {
let mut tmp = attrs.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 !mango_orm::store::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 !mango_orm::store::REGEX_IS_PASSWORD.is_match(value) {
Err("Allowed characters: a-z A-Z 0-9 @ # $ % ^ & + = * ! ~ ) (<br> \
Minimum size 8 characters")?
}
}
"InputDate" => {
if !mango_orm::store::REGEX_IS_DATE.is_match(value) {
Err("Incorrect date format.<br>Example: 1970-02-28")?
}
}
"InputDateTime" => {
if !mango_orm::store::REGEX_IS_DATETIME.is_match(value) {
Err("Incorrect date and time format.<br>Example: 1970-02-28T00:00")?
}
}
_ => return Ok(()),
}
Ok(())
}
pub fn create_password_hash(field_value: &str) -> Result<String, Box<dyn std::error::Error>> {
const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
abcdefghijklmnopqrstuvwxyz\
0123456789@#$%^&+=*!~)(";
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)
}
pub async fn check(&self, client: &mongodb::Client, output_format: mango_orm::forms::OutputType) ->
Result<mango_orm::forms::OutputData, Box<dyn std::error::Error>> {
static MODEL_NAME: &str = stringify!($sname);
let (mut store, key) = Self::form_cache().await?;
let meta: mango_orm::models::Meta = Self::metadata()?;
let mut global_err = false;
let is_update: bool = !self.hash.is_empty();
let mut attrs_map: std::collections::HashMap<String, mango_orm::widgets::Transport> = std::collections::HashMap::new();
let ignore_fields: Vec<&str> = meta.ignore_fields;
let coll: mongodb::Collection = client.database(&meta.database).collection(&meta.collection);
let mut doc_pre: mongodb::bson::document::Document = mongodb::bson::to_document(self).unwrap();
let mut doc_from_db: mongodb::bson::document::Document = if is_update {
let object_id = mongodb::bson::oid::ObjectId::with_string(&self.hash)
.unwrap_or_else(|err| { panic!("{:?}", err) });
let filter: mongodb::bson::document::Document = mongodb::bson::doc!{"_id": object_id};
coll.find_one(filter, None).await?.unwrap()
} else {
mongodb::bson::doc! {}
};
let mut doc_res: mongodb::bson::document::Document = mongodb::bson::doc! {};
let cache: Option<&mango_orm::store::FormCache> = store.get(&key);
if cache.is_some() {
let cache: &mango_orm::store::FormCache = cache.unwrap();
static FIELD_NAMES: &'static [&'static str] = &[$(stringify!($fname)),*];
attrs_map = cache.attrs_map.clone();
let widget_map: std::collections::HashMap<String, String> = cache.widget_map.clone();
{
let error_map: std::collections::HashMap<&str, &str> = self.custom_check()?;
if !error_map.is_empty() { global_err = true; }
for (field_name, err_msg) in error_map {
let attrs: &mut mango_orm::widgets::Transport = attrs_map.get_mut(field_name).unwrap();
attrs.error = Self::accumula_err(&attrs, &err_msg.to_string()).unwrap();
}
}
for field_name in FIELD_NAMES {
if field_name == &"hash" { continue; }
let value_bson_pre: Option<&mongodb::bson::Bson> = doc_pre.get(field_name);
if value_bson_pre.is_none() {
Err(format!("Model: `{}` -> Field: `{}` -> Method: `check()` : \
This field is missing.",
MODEL_NAME, field_name))?
}
let value_bson_pre: &mongodb::bson::Bson = value_bson_pre.unwrap();
let field_type: &str =
widget_map.get(&field_name.to_string()).unwrap();
let attrs: &mut mango_orm::widgets::Transport =
attrs_map.get_mut(&field_name.to_string()).unwrap();
let mut local_err = false;
match field_type {
"InputCheckBoxText" | "InputRadioText" | "InputColor"
| "InputEmail" | "InputPassword" | "InputTel"
| "InputText" | "InputUrl" | "InputIP" | "InputIPv4"
| "InputIPv6" | "TextArea" | "SelectText" => {
let field_value: &str = value_bson_pre.as_str().unwrap();
if attrs.required && field_value.is_empty() {
global_err = true;
local_err = true;
attrs.error =
Self::accumula_err(&attrs,
&"Required field.".to_owned()).unwrap();
attrs.value = field_value.to_string();
continue;
}
if is_update && !ignore_fields.contains(field_name) &&
((!attrs.required && field_value.is_empty()) ||
field_type == "InputPassword") {
let value_from_db: Option<&mongodb::bson::Bson> =
doc_from_db.get(&field_name);
if value_from_db.is_some() {
doc_res.insert(field_name.to_string(),
value_from_db.unwrap());
continue;
} else {
Err(format!("Model: `{}` -> Field: `{}` -> Method: \
`check()` : \
This field is missing from the database.",
MODEL_NAME, &field_name))?
}
}
Self::check_maxlength(attrs.maxlength, field_value)
.unwrap_or_else(|err| {
global_err = true;
local_err = true;
attrs.error =
Self::accumula_err(&attrs, &err.to_string()).unwrap();
});
if !field_value.is_empty() {
let min: f64 = attrs.min.parse().unwrap();
let max: f64 = attrs.max.parse().unwrap();
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) {
global_err = true;
local_err = true;
let msg = format!(
"Length {} is out of range (min={} <> max={}).",
len, min, max);
attrs.error = Self::accumula_err(&attrs, &msg).unwrap();
}
Self::check_unique(is_update, attrs.unique,
field_name.to_string(), value_bson_pre, "str", &coll)
.await.unwrap_or_else(|err| {
global_err = true;
local_err = true;
attrs.error =
Self::accumula_err(&attrs, &err.to_string())
.unwrap();
});
Self::regex_validation(field_type, field_value)
.unwrap_or_else(|err| {
global_err = true;
local_err = true;
attrs.error =
Self::accumula_err(&attrs, &err.to_string())
.unwrap();
});
}
if !local_err && !ignore_fields.contains(field_name) {
match field_type {
"InputPassword" => {
if !field_value.is_empty() {
let hash: String =
Self::create_password_hash(field_value)?;
doc_res.insert(field_name.to_string(),
mongodb::bson::Bson::String(hash));
}
}
_ => {
attrs.value = field_value.to_string();
doc_res.insert(field_name.to_string(),
mongodb::bson::Bson::String(field_value.to_string()));
}
}
}
}
"InputDate" | "InputDateTime" => {
let field_value: &str = value_bson_pre.as_str().unwrap();
if attrs.required && field_value.is_empty() {
global_err = true;
local_err = true;
attrs.error =
Self::accumula_err(&attrs,
&"Required field.".to_owned()).unwrap();
attrs.value = field_value.to_string();
continue;
}
if is_update && !ignore_fields.contains(field_name)
&& !attrs.required && field_value.is_empty() {
let value_from_db: Option<&mongodb::bson::Bson> =
doc_from_db.get(&field_name);
if value_from_db.is_some() {
doc_res.insert(field_name.to_string(),
value_from_db.unwrap());
continue;
} else {
Err(format!("Model: `{}` -> Field: `{}` -> Method: \
`check()` : \
This field is missing from the database.",
MODEL_NAME, &field_name))?
}
}
if field_value.is_empty() { continue; }
Self::regex_validation(field_type, field_value)
.unwrap_or_else(|err| {
global_err = true;
local_err = true;
attrs.error =
Self::accumula_err(&attrs, &err.to_string())
.unwrap();
});
if local_err { continue; }
let dt_value: chrono::DateTime<chrono::Utc> = {
let field_value: String = if field_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 !attrs.min.is_empty() && !attrs.max.is_empty() {
let dt_min: chrono::DateTime<chrono::Utc> = {
let min_value: String = if field_type == "InputDate" {
format!("{}T00:00", attrs.min.clone())
} else {
attrs.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 field_type == "InputDate" {
format!("{}T00:00", attrs.max.clone())
} else {
attrs.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 {
global_err = true;
attrs.error =
Self::accumula_err(&attrs,
&"Date out of range between `min` and` max`."
.to_owned()
).unwrap();
continue;
}
}
let dt_value_bson = mongodb::bson::Bson::DateTime(dt_value);
Self::check_unique(is_update, attrs.unique
, field_name.to_string(), &dt_value_bson
, "datetime", &coll)
.await.unwrap_or_else(|err| {
global_err = true;
local_err = true;
attrs.error =
Self::accumula_err(&attrs, &err.to_string())
.unwrap();
});
if !local_err {
doc_res.insert(field_name.to_string(),
dt_value_bson);
} else {
doc_res.insert(field_name.to_string(), mongodb::bson::Bson::Null);
}
}
"InputCheckBoxI32" | "InputRadioI32" | "InputNumberI32"
| "InputRangeI32" | "SelectI32" => {
let field_value: i32 = value_bson_pre.as_i32().unwrap();
Self::check_unique(is_update, attrs.unique,
field_name.to_string(), value_bson_pre, "i32", &coll)
.await.unwrap_or_else(|err| {
global_err = true;
local_err = true;
attrs.error =
Self::accumula_err(&attrs, &err.to_string())
.unwrap();
});
let min: f64 = attrs.min.parse().unwrap();
let max: f64 = attrs.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) {
global_err = true;
local_err = true;
let msg = format!(
"Number {} is out of range (min={} <> max={}).",
num, min, max);
attrs.error = Self::accumula_err(&attrs, &msg).unwrap();
}
if !local_err && !ignore_fields.contains(field_name) {
attrs.value = field_value.to_string();
doc_res.insert(field_name.to_string(),
mongodb::bson::Bson::Int32(field_value));
}
}
"InputCheckBoxU32" | "InputRadioU32" | "InputNumberU32"
| "InputRangeU32" | "SelectU32" | "InputCheckBoxI64"
| "InputRadioI64" | "InputNumberI64" | "InputRangeI64"
| "SelectI64" => {
let field_value: i64 = value_bson_pre.as_i64().unwrap();
Self::check_unique(is_update, attrs.unique,
field_name.to_string(), value_bson_pre, "i64", &coll)
.await.unwrap_or_else(|err| {
global_err = true;
local_err = true;
attrs.error =
Self::accumula_err(&attrs, &err.to_string())
.unwrap();
});
let min: f64 = attrs.min.parse().unwrap();
let max: f64 = attrs.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) {
global_err = true;
local_err = true;
let msg = format!(
"Number {} is out of range (min={} <> max={}).",
num, min, max);
attrs.error = Self::accumula_err(&attrs, &msg).unwrap();
}
if !local_err && !ignore_fields.contains(field_name) {
attrs.value = field_value.to_string();
doc_res.insert(field_name.to_string(),
mongodb::bson::Bson::Int64(field_value));
}
}
"InputCheckBoxF64" | "InputRadioF64" | "InputNumberF64"
| "InputRangeF64" | "SelectF64" => {
let field_value: f64 = value_bson_pre.as_f64().unwrap();
Self::check_unique(is_update, attrs.unique,
field_name.to_string(), value_bson_pre, "f64", &coll)
.await.unwrap_or_else(|err| {
global_err = true;
local_err = true;
attrs.error =
Self::accumula_err(&attrs, &err.to_string())
.unwrap();
});
let min: f64 = attrs.min.parse().unwrap();
let max: f64 = attrs.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) {
global_err = true;
local_err = true;
let msg = format!(
"Number {} is out of range (min={} <> max={}).",
num, min, max);
attrs.error = Self::accumula_err(&attrs, &msg).unwrap();
}
if !local_err && !ignore_fields.contains(field_name) {
attrs.value = field_value.to_string();
doc_res.insert(field_name.to_string(),
mongodb::bson::Bson::Double(field_value));
}
}
"InputCheckBoxBool" => {
let field_value: bool = value_bson_pre.as_bool().unwrap();
Self::check_unique(is_update, attrs.unique,
field_name.to_string(), value_bson_pre, "bool", &coll)
.await.unwrap_or_else(|err| {
global_err = true;
local_err = true;
attrs.error =
Self::accumula_err(&attrs, &err.to_string())
.unwrap();
});
if !local_err && !ignore_fields.contains(field_name) {
attrs.value = field_value.to_string();
doc_res.insert(field_name.to_string(),
mongodb::bson::Bson::Boolean(field_value));
}
}
_ => {
Err(format!("Model: `{}` -> Field: `{}` -> Method: \
`check()` : Unsupported data type.",
MODEL_NAME, field_name))?
}
}
if !global_err {
let dt: chrono::DateTime<chrono::Utc> = chrono::Utc::now();
if !is_update {
doc_res.insert("created".to_string(), mongodb::bson::Bson::DateTime(dt));
doc_res.insert("updated".to_string(), mongodb::bson::Bson::DateTime(dt));
} else {
let value_from_db: Option<&mongodb::bson::Bson> = doc_from_db.get("created");
if value_from_db.is_some() {
doc_res.insert("created".to_string(), value_from_db.unwrap());
doc_res.insert("updated".to_string(), mongodb::bson::Bson::DateTime(dt));
} else {
Err(format!("Model: `{}` -> Field: `created` -> Method: \
`check()` : Can't get field value from database.",
MODEL_NAME))?
}
}
}
}
} else {
Err(format!("Model: `{}` -> Method: `check()` : \
Did not receive data from cache.",
MODEL_NAME))?
}
let result: mango_orm::forms::OutputData = match output_format {
mango_orm::forms::OutputType::Hash => {
let data: String = Self::to_hash(&attrs_map)?;
mango_orm::forms::OutputData::Hash((data, !global_err, doc_res))
}
mango_orm::forms::OutputType::Map => mango_orm::forms::OutputData::Map((attrs_map, !global_err, doc_res)),
mango_orm::forms::OutputType::Json => {
let data: String = Self::to_json(&attrs_map)?;
mango_orm::forms::OutputData::Json((data, !global_err, doc_res))
}
mango_orm::forms::OutputType::Html => {
let data: String = Self::to_html(attrs_map)?;
mango_orm::forms::OutputData::Html((data, !global_err, doc_res))
}
};
Ok(result)
}
pub fn to_hash(attrs_map: &std::collections::HashMap<String, mango_orm::widgets::Transport>) ->
Result<String, Box<dyn std::error::Error>> {
let mut errors = String::new();
for (field, trans) in attrs_map {
let tmp = if !errors.is_empty() {
format!("{} ; ", errors)
} else {
String::new()
};
if !trans.error.is_empty() {
errors = format!("{}Field: `{}` - {}", tmp, field, trans.error);
}
}
if errors.is_empty() {
Ok(attrs_map
.get(&"hash".to_string())
.unwrap()
.value
.clone())
} else {
Err(errors.replace("<br>", " | "))?
}
}
pub fn to_json(attrs_map: &std::collections::HashMap<String, mango_orm::widgets::Transport>) ->
Result<String, Box<dyn std::error::Error>> {
let mut json_text = String::new();
for (field, trans) in attrs_map {
let tmp = serde_json::to_string(&trans).unwrap();
if !json_text.is_empty() {
json_text = format!("{},\"{}\":{}", json_text, field, tmp);
} else {
json_text = format!("\"{}\":{}", field, tmp);
}
}
Ok(format!("{{{}}}", json_text))
}
pub fn to_html(attrs_map: std::collections::HashMap<String, mango_orm::widgets::Transport>) ->
Result<String, Box<dyn std::error::Error>> {
let controls = Self::html(
attrs_map,
&stringify!($sname).to_lowercase(),
true
)?;
Ok(controls)
}
pub async fn save(& mut self, client: &mongodb::Client, output_format: mango_orm::forms::OutputType) ->
Result<mango_orm::forms::OutputData, Box<dyn std::error::Error>> {
let verified_data: mango_orm::forms::OutputData = self.check(client, mango_orm::forms::OutputType::Map).await?;
let mut attrs_map: std::collections::HashMap<String, mango_orm::widgets::Transport> = verified_data.map();
let meta: mango_orm::models::Meta = Self::metadata()?;
let is_update: bool = !self.hash.is_empty();
let coll: mongodb::Collection = client.database(&meta.database).collection(&meta.collection);
if verified_data.bool() {
if !is_update {
let result: mongodb::results::InsertOneResult =
coll.insert_one(verified_data.doc(), None).await?;
self.hash = result.inserted_id.as_object_id().unwrap().to_hex();
} else {
let object_id = mongodb::bson::oid::ObjectId::with_string(&self.hash)
.unwrap_or_else(|err| { panic!("{}", err.to_string()) });
let query: mongodb::bson::document::Document = mongodb::bson::doc!{"_id": object_id};
coll.update_one(query, verified_data.doc(), None).await?;
}
}
attrs_map.get_mut(&"hash".to_string()).unwrap().value = self.hash.clone();
let result: mango_orm::forms::OutputData = match output_format {
mango_orm::forms::OutputType::Hash => {
let data: String = Self::to_hash(&attrs_map)?;
mango_orm::forms::OutputData::Hash((data, verified_data.bool(), verified_data.doc()))
}
mango_orm::forms::OutputType::Map => {
mango_orm::forms::OutputData::Map((attrs_map, verified_data.bool(), verified_data.doc()))
}
mango_orm::forms::OutputType::Json => {
let data: String = Self::to_json(&attrs_map)?;
mango_orm::forms::OutputData::Json((data, verified_data.bool(), verified_data.doc()))
}
mango_orm::forms::OutputType::Html => {
let data: String = Self::to_html(attrs_map)?;
mango_orm::forms::OutputData::Html((data, verified_data.bool(), verified_data.doc()))
}
};
Ok(result)
}
pub async fn migrat<'a>(client: &mongodb::Client, keyword: &'a str) {
static MODEL_NAME: &str = stringify!($sname);
static FIELD_NAMES: &'static [&'static str] = &[$(stringify!($fname)),*];
let meta: mango_orm::models::Meta = Self::metadata().unwrap();
let ignore_fields: Vec<&str> = meta.ignore_fields;
if meta.service.is_empty() || meta.database.is_empty() {
panic!(
"Service: `{}` -> Model: `{}` -> Method: `meta()` : \
The `service` and` database` fields must not be empty.",
meta.service, MODEL_NAME
)
}
if !FIELD_NAMES.contains(&"hash") {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `hash` : \
Add a `hash` field to the Model (`String` type).",
meta.service, MODEL_NAME
)
}
if FIELD_NAMES.contains(&"created") {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `created` : \
This field is reserved. Solution - Replace with a different name",
meta.service, MODEL_NAME
)
}
if FIELD_NAMES.contains(&"updated") {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `updated` : \
This field is reserved. Solution - Replace with a different name",
meta.service, MODEL_NAME
)
}
for field in ignore_fields.iter() {
if !FIELD_NAMES.contains(field) {
panic!(
"Service: `{}` -> Model: `{}` : \
The model structure is missing an ignored `{}` field.",
meta.service, MODEL_NAME, field
)
}
}
let field_names_without_auxiliary: Vec<&str> = FIELD_NAMES.iter()
.map(|field| field.clone())
.filter(|field| field != &"hash" && !ignore_fields.contains(field)).collect();
if field_names_without_auxiliary.is_empty() {
panic!("Service: `{}` -> Model: `{}` -> Method: `migrat()` : \
The model structure has no fields.",
meta.service, MODEL_NAME);
}
let map_field_types: std::collections::HashMap<&str, &str> =
FIELD_NAMES.iter().map(|item| item.to_owned())
.zip([$(stringify!($ftype)),*].iter().map(|item| item.to_owned())).collect();
let meta: mango_orm::models::Meta = Self::metadata().unwrap();
let mango_orm_keyword = format!("mango_orm_{}", keyword);
let map_widgets: std::collections::HashMap<&str, mango_orm::widgets::Widget> = Self::widgets_full_map().unwrap();
let database_names: Vec<String> =
client.list_database_names(None, None).await.unwrap();
let mut default_values: std::collections::HashMap<&str, (&str, String)> = std::collections::HashMap::new();
for (field, widget) in map_widgets {
if !FIELD_NAMES.contains(&field) {
panic!(
"Service: `{}` -> Model: `{}` -> widgets() : \
`{}` - Incorrect field name.",
meta.service, MODEL_NAME, field
)
}
default_values.insert(field,
(widget.value.get_enum_type(), widget.value.get_raw_data()));
match widget.value {
mango_orm::widgets::FieldType::Hash => {
let enum_field_type = "Hash".to_string();
let data_field_type = "String".to_string();
if map_field_types[field] != "String" {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` : \
Field type is not equal to `String`.",
meta.service, MODEL_NAME, field
)
}
}
mango_orm::widgets::FieldType::InputCheckBoxText(_) | mango_orm::widgets::FieldType::InputCheckBoxI32(_)
| mango_orm::widgets::FieldType::InputCheckBoxU32(_) | mango_orm::widgets::FieldType::InputCheckBoxI64(_)
| mango_orm::widgets::FieldType::InputCheckBoxF64(_) => {
let mut enum_field_type = String::new();
let mut data_field_type = String::new();
match widget.value {
mango_orm::widgets::FieldType::InputCheckBoxText(_) => {
enum_field_type = "InputCheckBoxText".to_string();
data_field_type = "String".to_string();
}
mango_orm::widgets::FieldType::InputCheckBoxI32(_) => {
enum_field_type = "InputCheckBoxI32".to_string();
data_field_type = "i32".to_string();
}
mango_orm::widgets::FieldType::InputCheckBoxU32(_) => {
enum_field_type = "InputCheckBoxU32".to_string();
data_field_type = "i64".to_string();
}
mango_orm::widgets::FieldType::InputCheckBoxI64(_) => {
enum_field_type = "InputCheckBoxI64".to_string();
data_field_type = "i64".to_string();
}
mango_orm::widgets::FieldType::InputCheckBoxF64(_) => {
enum_field_type = "InputCheckBoxF64".to_string();
data_field_type = "f64".to_string();
}
_ => panic!("Invalid field type")
}
if widget.relation_model != String::new() {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> For `value` = mango_orm::widgets::FieldType::`{}` : \
`relation_model` = only blank string.",
meta.service, MODEL_NAME, field, enum_field_type
)
} else if widget.maxlength != 0 {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> For `value` = mango_orm::widgets::FieldType::`{}` : \
`maxlength` = only 0 (zero).",
meta.service, MODEL_NAME, field, enum_field_type
)
} else if widget.step.get_enum_type() != widget.min.get_enum_type()
|| widget.step.get_enum_type() != widget.max.get_enum_type() {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` : The `step`, `min` and `max` \
fields must have the same types.",
meta.service, MODEL_NAME, field
)
} else if widget.other_attrs.contains("checked") {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> For `value` = mango_orm::widgets::FieldType::`{}` : \
`other_attrs` - must not contain the word `checked`.",
meta.service, MODEL_NAME, field, enum_field_type
)
} else if !widget.select.is_empty() {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> For `value` = mango_orm::widgets::FieldType::`{}` : \
`select` = only blank vec![].",
meta.service, MODEL_NAME, field, enum_field_type
)
} else if data_field_type != map_field_types[field] {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` : \
Field type is not equal to `{}`.",
meta.service, MODEL_NAME, field, map_field_types[field]
)
}
}
mango_orm::widgets::FieldType::InputColor(_) | mango_orm::widgets::FieldType::InputEmail(_)
| mango_orm::widgets::FieldType::InputPassword(_) | mango_orm::widgets::FieldType::InputText(_)
| mango_orm::widgets::FieldType::InputUrl(_) | mango_orm::widgets::FieldType::InputIP(_)
| mango_orm::widgets::FieldType::InputIPv4(_) | mango_orm::widgets::FieldType::InputIPv6(_)
| mango_orm::widgets::FieldType::TextArea(_) => {
let mut enum_field_type = String::new();
match widget.value {
mango_orm::widgets::FieldType::InputColor(_) => {
enum_field_type = "InputColor".to_string();
}
mango_orm::widgets::FieldType::InputEmail(_) => {
enum_field_type = "InputEmail".to_string();
}
mango_orm::widgets::FieldType::InputPassword(_) => {
enum_field_type = "InputPassword".to_string();
}
mango_orm::widgets::FieldType::InputText(_) => {
enum_field_type = "InputText".to_string();
}
mango_orm::widgets::FieldType::InputUrl(_) => {
enum_field_type = "InputUrl".to_string();
}
mango_orm::widgets::FieldType::TextArea(_) => {
enum_field_type = "TextArea".to_string();
}
_ => panic!("Invalid field type")
}
if widget.relation_model != String::new() {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> For `value` = mango_orm::widgets::FieldType::`{}` : \
`relation_model` = only blank string.",
meta.service, MODEL_NAME, field, enum_field_type
)
} else if widget.step.get_enum_type() != "U32"
|| widget.min.get_enum_type() != "U32"
|| widget.max.get_enum_type() != "U32" {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` : The attributes `min` and `max` \
must be of type `DataType::U32`.",
meta.service, MODEL_NAME, field
)
} else if !widget.select.is_empty() {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> For `value` = mango_orm::widgets::FieldType::`{}` : \
`select` = only blank vec![].",
meta.service, MODEL_NAME, field, enum_field_type
)
} else if map_field_types[field] != "String" {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` : \
Field type is not equal to `String`.",
meta.service, MODEL_NAME, field
)
}
}
mango_orm::widgets::FieldType::InputDate(_) | mango_orm::widgets::FieldType::InputDateTime(_) => {
let mut enum_field_type = String::new();
match widget.value {
mango_orm::widgets::FieldType::InputDate(_) => {
enum_field_type = "InputDate".to_string();
}
mango_orm::widgets::FieldType::InputDateTime(_) => {
enum_field_type = "InputDateTime".to_string();
}
_ => panic!("Invalid field type")
}
if widget.relation_model != String::new() {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` ->\
Method: `widgets()` -> For `value` = mango_orm::widgets::FieldType::`{}` : \
`relation_model` = only blank string.",
meta.service, MODEL_NAME, field, enum_field_type
)
} else if widget.step.get_enum_type() != "U32" {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` : The attribute `step` must be of type \
`DataType::U32`.",
meta.service, MODEL_NAME, field
)
} else if widget.min.get_enum_type() != "Text"
|| widget.max.get_enum_type() != "Text" {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` : The attributes `min` and `max` \
must be of type `DataType::Text`.",
meta.service, MODEL_NAME, field
)
} else if !widget.select.is_empty() {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> For `value` = mango_orm::widgets::FieldType::`{}` : \
`select` = only blank vec![].",
meta.service, MODEL_NAME, field, enum_field_type
)
} else if map_field_types[field] != "String" {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` : \
Field type is not equal to `String`.",
meta.service, MODEL_NAME, field
)
} else if widget.min.get_raw_data().len()
!= widget.max.get_raw_data().len() {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` : The `min` and` max` \
attributes must be in \
the appropriate format `1970-02-28` or \
` 1970-02-28T00:00` or an empty strings.",
meta.service, MODEL_NAME, field
)
} else if !widget.min.get_raw_data().is_empty()
&& !widget.max.get_raw_data().is_empty() {
let mut date_min: String = widget.min.get_raw_data();
let mut date_max: String = widget.max.get_raw_data();
let mut date_value: String = widget.value.get_raw_data();
match widget.value {
mango_orm::widgets::FieldType::InputDate(_) => {
if !mango_orm::store::REGEX_IS_DATE.is_match(&date_min) {
panic!("Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> Attribute: `min` : \
Incorrect date format. Example: 1970-02-28",
meta.service, MODEL_NAME, field)
}
if !mango_orm::store::REGEX_IS_DATE.is_match(&date_max) {
panic!("Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> Attribute: `max` : \
Incorrect date format. Example: 1970-02-28",
meta.service, MODEL_NAME, field)
}
if !date_value.is_empty() {
if !mango_orm::store::REGEX_IS_DATE.is_match(&date_value) {
panic!("Service: `{}` -> Model: `{}` -> \
Field: `{}` -> Method: `widgets()` -> \
Attribute: `value` : Incorrect date \
format. Example: 1970-02-28",
meta.service, MODEL_NAME, field)
}
date_value = format!("{}T00:00", date_value);
}
date_min = format!("{}T00:00", date_min);
date_max = format!("{}T00:00", date_max);
}
mango_orm::widgets::FieldType::InputDateTime(_) => {
if !mango_orm::store::REGEX_IS_DATETIME.is_match(&date_min) {
panic!("Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> Attribute: `min` : \
Incorrect date format. \
Example: 1970-02-28T00:00",
meta.service, MODEL_NAME, field)
}
if !mango_orm::store::REGEX_IS_DATETIME.is_match(&date_max) {
panic!("Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> Attribute: `max` : \
Incorrect date format. \
Example: 1970-02-28T00:00",
meta.service, MODEL_NAME, field)
}
if !date_value.is_empty() {
if !mango_orm::store::REGEX_IS_DATETIME.is_match(&date_value) {
panic!("Service: `{}` -> Model: `{}` -> \
Field: `{}` -> Method: `widgets()` -> \
Attribute: `value` : Incorrect date \
format. Example: 1970-02-28T00:00",
meta.service, MODEL_NAME, field)
}
}
}
_ => {
panic!("Invalid field type")
}
}
let dt_min: chrono::DateTime<chrono::Utc> =
chrono::DateTime::<chrono::Utc>::from_utc(
chrono::NaiveDateTime::parse_from_str(
&date_min, "%Y-%m-%dT%H:%M").unwrap(), chrono::Utc);
let dt_max: chrono::DateTime<chrono::Utc> =
chrono::DateTime::<chrono::Utc>::from_utc(
chrono::NaiveDateTime::parse_from_str(
&date_max, "%Y-%m-%dT%H:%M").unwrap(), chrono::Utc);
if dt_min >= dt_max {
panic!("Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> Attribute: `min` : \
Must be less than `max`.",
meta.service, MODEL_NAME, field)
} else if !date_value.is_empty() {
let dt_value: chrono::DateTime<chrono::Utc> =
chrono::DateTime::<chrono::Utc>::from_utc(
chrono::NaiveDateTime::parse_from_str(
&date_value, "%Y-%m-%dT%H:%M").unwrap(), chrono::Utc);
if dt_value < dt_min || dt_value > dt_max {
panic!("Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> Attribute: `value` : \
Out of range between `min` and` max`.",
meta.service, MODEL_NAME, field)
}
}
}
}
mango_orm::widgets::FieldType::InputFile | mango_orm::widgets::FieldType::InputImage => {
let mut enum_field_type = String::new();
match widget.value {
mango_orm::widgets::FieldType::InputFile => {
enum_field_type = "InputFile".to_string();
}
mango_orm::widgets::FieldType::InputImage => {
enum_field_type = "InputImage".to_string();
}
_ => panic!("Invalid field type")
}
if widget.relation_model != String::new() {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> For `value` = mango_orm::widgets::FieldType::`{}` : \
`relation_model` = only blank string.",
meta.service, MODEL_NAME, field, enum_field_type
)
} else if widget.step.get_enum_type() != widget.min.get_enum_type()
|| widget.step.get_enum_type() != widget.max.get_enum_type() {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` : The `step`, `min` and `max` \
attributes must have the same types.",
meta.service, MODEL_NAME, field
)
} else if !widget.select.is_empty() {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> For `value` = mango_orm::widgets::FieldType::`{}` : \
`select` = only blank vec![].",
meta.service, MODEL_NAME, field, enum_field_type
)
} else if map_field_types[field] != "String" {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` : \
Field type is not equal to `String`.",
meta.service, MODEL_NAME, field
)
}
}
mango_orm::widgets::FieldType::InputNumberI32(_) | mango_orm::widgets::FieldType::InputNumberU32(_)
| mango_orm::widgets::FieldType::InputNumberI64(_) | mango_orm::widgets::FieldType::InputNumberF64(_) => {
let mut enum_field_type = String::new();
let mut data_field_type = String::new();
let mut step_min_max_enum_type = String::new();
match widget.value {
mango_orm::widgets::FieldType::InputNumberI32(_) => {
enum_field_type = "InputNumberI32".to_string();
data_field_type = "i32".to_string();
step_min_max_enum_type = "I32".to_string();
}
mango_orm::widgets::FieldType::InputNumberU32(_) => {
enum_field_type = "InputNumberU32".to_string();
data_field_type = "i64".to_string();
step_min_max_enum_type = "U32".to_string();
}
mango_orm::widgets::FieldType::InputNumberI64(_) => {
enum_field_type = "InputNumberI64".to_string();
data_field_type = "i64".to_string();
step_min_max_enum_type = "I64".to_string();
}
mango_orm::widgets::FieldType::InputNumberF64(_) => {
enum_field_type = "InputNumberF64".to_string();
data_field_type = "f64".to_string();
step_min_max_enum_type = "F64".to_string();
}
_ => panic!("Invalid field type")
}
if widget.relation_model != String::new() {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> For `value` = mango_orm::widgets::FieldType::`{}` : \
`relation_model` = only blank string.",
meta.service, MODEL_NAME, field, enum_field_type
)
} else if !widget.select.is_empty() {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> For `value` = mango_orm::widgets::FieldType::`{}` : \
`select` = only blank vec![].",
meta.service, MODEL_NAME, field, enum_field_type
)
} else if data_field_type != map_field_types[field] {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` : \
Field type is not equal to `{}`.",
meta.service, MODEL_NAME, field, map_field_types[field]
)
} else if widget.step.get_data_type() != data_field_type {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> For `value` = mango_orm::widgets::FieldType::`{}` : \
`step` = `{}`.",
meta.service, MODEL_NAME, field, enum_field_type,
step_min_max_enum_type
)
} else if widget.min.get_data_type() != data_field_type {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> For `value` = mango_orm::widgets::FieldType::`{}` : \
`min` = `{}`.",
meta.service, MODEL_NAME, field, enum_field_type,
step_min_max_enum_type
)
} else if widget.max.get_data_type() != data_field_type {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> For `value` = mango_orm::widgets::FieldType::`{}` : \
`max` = `{}`.",
meta.service, MODEL_NAME, field, enum_field_type,
step_min_max_enum_type
)
}
}
mango_orm::widgets::FieldType::InputRadioText(_) | mango_orm::widgets::FieldType::InputRadioI32(_)
| mango_orm::widgets::FieldType::InputRadioU32(_) | mango_orm::widgets::FieldType::InputRadioI64(_)
| mango_orm::widgets::FieldType::InputRadioF64(_) => {
let mut enum_field_type = String::new();
let mut data_field_type = String::new();
match widget.value {
mango_orm::widgets::FieldType::InputRadioText(_) => {
enum_field_type = "InputRadioText".to_string();
data_field_type = "String".to_string();
}
mango_orm::widgets::FieldType::InputRadioI32(_) => {
enum_field_type = "InputRadioI32".to_string();
data_field_type = "i32".to_string();
}
mango_orm::widgets::FieldType::InputRadioU32(_) => {
enum_field_type = "InputRadioU32".to_string();
data_field_type = "i64".to_string();
}
mango_orm::widgets::FieldType::InputRadioI64(_) => {
enum_field_type = "InputRadioI64".to_string();
data_field_type = "i64".to_string();
}
mango_orm::widgets::FieldType::InputRadioF64(_) => {
enum_field_type = "InputRadioF64".to_string();
data_field_type = "f64".to_string();
}
_ => panic!("Invalid field type")
}
if widget.relation_model != String::new() {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> For `value` = mango_orm::widgets::FieldType::`{}` : \
`relation_model` = only blank string.",
meta.service, MODEL_NAME, field, enum_field_type
)
} else if widget.maxlength != 0 {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> For `value` = mango_orm::widgets::FieldType::`{}` : \
`maxlength` = only 0 (zero).",
meta.service, MODEL_NAME, field, enum_field_type
)
} else if widget.step.get_enum_type() != widget.min.get_enum_type()
|| widget.step.get_enum_type() != widget.max.get_enum_type() {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` : The `step`, `min` and `max` \
attributes must have the same types.",
meta.service, MODEL_NAME, field
)
} else if widget.other_attrs.contains("checked") {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> For `value` = mango_orm::widgets::FieldType::`{}` : \
`other_attrs` - must not contain the word `checked`.",
meta.service, MODEL_NAME, field, enum_field_type
)
} else if widget.select.is_empty() {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> For `value` = mango_orm::widgets::FieldType::`{}` : \
`select` - must not be an empty vec![]",
meta.service, MODEL_NAME, field, enum_field_type
)
} else if data_field_type != map_field_types[field] {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` : \
Field type is not equal to `{}`.",
meta.service, MODEL_NAME, field, map_field_types[field]
)
}
}
mango_orm::widgets::FieldType::InputRangeI32(_) | mango_orm::widgets::FieldType::InputRangeU32(_)
| mango_orm::widgets::FieldType::InputRangeI64(_) | mango_orm::widgets::FieldType::InputRangeF64(_) => {
let mut enum_field_type = String::new();
let mut data_field_type = String::new();
let mut step_min_max_enum_type = String::new();
match widget.value {
mango_orm::widgets::FieldType::InputRangeI32(_) => {
enum_field_type = "InputRangeI32".to_string();
data_field_type = "i32".to_string();
step_min_max_enum_type = "I32".to_string();
}
mango_orm::widgets::FieldType::InputRangeU32(_) => {
enum_field_type = "InputRangeU32".to_string();
data_field_type = "i64".to_string();
step_min_max_enum_type = "U32".to_string();
}
mango_orm::widgets::FieldType::InputRangeI64(_) => {
enum_field_type = "InputRangeI64".to_string();
data_field_type = "i64".to_string();
step_min_max_enum_type = "I64".to_string();
}
mango_orm::widgets::FieldType::InputRangeF64(_) => {
enum_field_type = "InputRangeI64".to_string();
data_field_type = "f64".to_string();
step_min_max_enum_type = "F64".to_string();
}
_ => panic!("Invalid field type")
}
if widget.relation_model != String::new() {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> For `value` = mango_orm::widgets::FieldType::`{}` : \
`relation_model` = only blank string.",
meta.service, MODEL_NAME, field, enum_field_type
)
} else if !widget.select.is_empty() {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> For `value` = mango_orm::widgets::FieldType::`{}` : \
`select` = only blank vec![].",
meta.service, MODEL_NAME, field, enum_field_type
)
} else if data_field_type != map_field_types[field] {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` : \
Field type is not equal to `{}`.",
meta.service, MODEL_NAME, field, map_field_types[field]
)
} else if widget.step.get_data_type() != data_field_type {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> For `value` = mango_orm::widgets::FieldType::`{}` : \
`step` = `{}`.",
meta.service, MODEL_NAME, field, enum_field_type,
step_min_max_enum_type
)
} else if widget.min.get_data_type() != data_field_type {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> For `value` = mango_orm::widgets::FieldType::`{}` : \
`min` = `{}`.",
meta.service, MODEL_NAME, field, enum_field_type,
step_min_max_enum_type
)
} else if widget.max.get_data_type() != data_field_type {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> For `value` = mango_orm::widgets::FieldType::`{}` : \
`max` = `{}`.",
meta.service, MODEL_NAME, field, enum_field_type,
step_min_max_enum_type
)
}
}
mango_orm::widgets::FieldType::SelectText(_) | mango_orm::widgets::FieldType::SelectI32(_)
| mango_orm::widgets::FieldType::SelectU32(_) | mango_orm::widgets::FieldType::SelectI64(_)
| mango_orm::widgets::FieldType::SelectF64(_) => {
let mut enum_field_type = String::new();
let mut data_field_type = String::new();
match widget.value {
mango_orm::widgets::FieldType::SelectText(_) => {
enum_field_type = "SelectText".to_string();
data_field_type = "String".to_string();
}
mango_orm::widgets::FieldType::SelectI32(_) => {
enum_field_type = "SelectI32".to_string();
data_field_type = "i32".to_string();
}
mango_orm::widgets::FieldType::SelectU32(_) => {
enum_field_type = "SelectU32".to_string();
data_field_type = "i64".to_string();
}
mango_orm::widgets::FieldType::SelectI64(_) => {
enum_field_type = "SelectI64".to_string();
data_field_type = "i64".to_string();
}
mango_orm::widgets::FieldType::SelectF64(_) => {
enum_field_type = "SelectF64".to_string();
data_field_type = "f64".to_string();
}
_ => panic!("Invalid field type")
}
if widget.relation_model != String::new() {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> For `value` = mango_orm::widgets::FieldType::`{}` : \
`relation_model` = only blank string.",
meta.service, MODEL_NAME, field, enum_field_type
)
} else if widget.step.get_enum_type() != widget.min.get_enum_type()
|| widget.step.get_enum_type() != widget.max.get_enum_type() {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` : The `step`, `min` and `max` \
attributes must have the same types.",
meta.service, MODEL_NAME, field
)
} else if widget.select.is_empty() {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> For `value` = mango_orm::widgets::FieldType::`{}` : \
`select` - Should not be empty.",
meta.service, MODEL_NAME, field, enum_field_type
)
} else if data_field_type != map_field_types[field] {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` : \
Field type is not equal to `{}`.",
meta.service, MODEL_NAME, field, map_field_types[field]
)
}
}
mango_orm::widgets::FieldType::ForeignKey => {
if widget.relation_model == String::new() {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> For `value` = mango_orm::widgets::FieldType `ForeignKey` : \
`relation_model` = \
<CategoryName>::meta().collection.to_string().",
meta.service, MODEL_NAME, field
)
} else if widget.step.get_enum_type() != widget.min.get_enum_type()
|| widget.step.get_enum_type() != widget.max.get_enum_type() {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` : The `step`, `min` and `max` \
attributes must have the same types.",
meta.service, MODEL_NAME, field
)
} else if !widget.select.is_empty() {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> For `value` = \
mango_orm::widgets::FieldType `ForeignKey` : `select` = only blank vec![].",
meta.service, MODEL_NAME, field
)
} else if map_field_types[field] != "String" {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` : \
Field type is not equal to `String`.",
meta.service, MODEL_NAME, field
)
}
}
mango_orm::widgets::FieldType::ManyToMany => {
if widget.relation_model == String::new() {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> For `value` = \
mango_orm::widgets::FieldType `ManyToMany` : `relation_model` = \
<CategoryName>::meta().collection.to_string().",
meta.service, MODEL_NAME, field
)
} else if widget.step.get_enum_type() != widget.min.get_enum_type()
|| widget.step.get_enum_type() != widget.max.get_enum_type() {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` : The `step`, `min` and `max` \
attributes must have the same types.",
meta.service, MODEL_NAME, field
)
} else if !widget.select.is_empty() {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> For `value` = \
mango_orm::widgets::FieldType `ManyToMany` : `select` = only blank vec![].",
meta.service, MODEL_NAME, field
)
} else if map_field_types[field] != "String" {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` : \
Field type is not equal to `String`.",
meta.service, MODEL_NAME, field
)
}
}
mango_orm::widgets::FieldType::OneToOne => {
if widget.relation_model == String::new() {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> For `value` = \
mango_orm::widgets::FieldType `OneToOne` : `relation_model` = \
<CategoryName>::meta().collection.to_string().",
meta.service, MODEL_NAME, field
)
} else if widget.step.get_enum_type() != widget.min.get_enum_type()
|| widget.step.get_enum_type() != widget.max.get_enum_type() {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` : The `step`, `min` and `max` \
attributes must have the same types.",
meta.service, MODEL_NAME, field
)
} else if !widget.select.is_empty() {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `widgets()` -> For `value` = \
mango_orm::widgets::FieldType `OneToOne` : `select` = only blank vec![].",
meta.service, MODEL_NAME, field
)
} else if map_field_types[field] != "String" {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` : \
Field type is not equal to `String`.",
meta.service, MODEL_NAME, field
)
}
}
_ => panic!("Service: `{}` -> Model: `{}` -> Field: `{}` : \
`field_type` - Non-existent field type.",
meta.service, MODEL_NAME, field),
}
if widget.step.get_enum_type() != "Text"
&& widget.min.get_enum_type() != "Text"
&& widget.max.get_enum_type() != "Text" {
match widget.step.get_enum_type() {
"I32" => {
let step: i32 = widget.step.get_raw_data().parse().unwrap();
let min: i32 = widget.min.get_raw_data().parse().unwrap();
let max: i32 = widget.max.get_raw_data().parse().unwrap();
if step > 0_i32 || min > 0_i32 || max > 0_i32 {
if min > max {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
widgets : The `min` attribute must not be greater \
than `max`.",
meta.service, MODEL_NAME, field
)
} else if step > 0_i32 && (max - min) % step != 0_i32 {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
widgets : The value of the `step` attribute does not \
match the condition (max - min) % step == 0.",
meta.service, MODEL_NAME, field
)
}
}
}
"U32" | "I64" => {
let step: i64 = widget.step.get_raw_data().parse().unwrap();
let min: i64 = widget.min.get_raw_data().parse().unwrap();
let max: i64 = widget.max.get_raw_data().parse().unwrap();
if step > 0_i64 || min > 0_i64 || max > 0_i64 {
if min > max {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
widgets : The `min` attribute must not be greater \
than `max`.",
meta.service, MODEL_NAME, field
)
} else if step > 0_i64 && (max - min) % step != 0_i64 {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
widgets : The value of the `step` attribute does not \
match the condition (max - min) % step == 0.",
meta.service, MODEL_NAME, field
)
}
}
}
"F64" => {
let step: f64 = widget.step.get_raw_data().parse().unwrap();
let min: f64 = widget.min.get_raw_data().parse().unwrap();
let max: f64 = widget.max.get_raw_data().parse().unwrap();
if step > 0_f64 || min > 0_f64 || max > 0_f64 {
if min > max {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
widgets : The `min` attribute must not be greater \
than `max`.",
meta.service, MODEL_NAME, field
)
} else if step > 0_f64 && (max - min) % step != 0_f64 {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` -> \
widgets : The value of the `step` attribute does not \
match the condition (max - min) % step == 0.",
meta.service, MODEL_NAME, field
)
}
}
}
_ => {
panic!(
"Service: `{}` -> Model: `{}` -> Field: `{}` : \
Non-existent field type.",
meta.service, MODEL_NAME, field
)
}
}
}
}
let filter: mongodb::bson::document::Document = mongodb::bson::doc! {
"database": &meta.database,
"collection": &meta.collection
};
let model: Option<mongodb::bson::document::Document> = client.database(&mango_orm_keyword)
.collection("models").find_one(filter, None).await.unwrap();
if model.is_some() {
let mango_orm_fnames: Vec<String> = {
let model: mongodb::bson::document::Document = model.unwrap();
let fields: Vec<mongodb::bson::Bson> = model.get_array("fields").unwrap().to_vec();
fields.into_iter().map(|item: mongodb::bson::Bson| item.as_str().unwrap().to_string())
.collect()
};
let mut run_documents_modification: bool = false;
if field_names_without_auxiliary.len() != mango_orm_fnames.len() {
run_documents_modification = true;
} else {
for item in field_names_without_auxiliary {
if mango_orm_fnames.iter().any(|item2| item2 != &item) {
run_documents_modification = true;
break;
}
}
}
if run_documents_modification {
let db: mongodb::Database = client.database(&meta.database);
let collection: mongodb::Collection = db.collection(&meta.collection);
let mut cursor: mongodb::Cursor = collection.find(None, None).await.unwrap();
while let Some(result) = cursor.next().await {
let doc_from_db: mongodb::bson::document::Document = result.unwrap();
let mut tmp_doc = mongodb::bson::doc! {};
for field in FIELD_NAMES {
if field == &"hash" || ignore_fields.contains(field) {
continue;
}
if doc_from_db.contains_key(field) {
let value_from_db: Option<&mongodb::bson::Bson> = doc_from_db.get(field);
if value_from_db.is_some() {
tmp_doc.insert(field.to_string(), value_from_db.unwrap());
} else {
panic!("Service: `{}` -> Model: `{}` -> Field: `{}` -> \
Method: `migrat()` : \
Can't get field value from database.",
meta.service, MODEL_NAME, field);
}
} else {
let value = &default_values[field];
tmp_doc.insert(field.to_string(), match value.0 {
"InputCheckBoxText" | "InputRadioText" | "InputColor"
| "InputEmail" | "InputPassword" | "InputTel"
| "InputText" | "InputUrl" | "InputIP" | "InputIPv4"
| "InputIPv6" | "TextArea" | "SelectText" => {
mongodb::bson::Bson::String(value.1.clone())
}
"InputDate" => {
let mut val: String = value.1.clone();
if !val.is_empty() {
if !mango_orm::store::REGEX_IS_DATE.is_match(&val) {
panic!("Service: `{}` -> Model: `{}` -> \
Method: `widgets()` : Incorrect date \
format. Example: 1970-02-28",
meta.service, MODEL_NAME)
}
let val = format!("{}T00:00", val);
let dt: chrono::DateTime<chrono::Utc> =
chrono::DateTime::<chrono::Utc>::from_utc(
chrono::NaiveDateTime::parse_from_str(
&val, "%Y-%m-%dT%H:%M").unwrap(), chrono::Utc);
mongodb::bson::Bson::DateTime(dt)
} else {
mongodb::bson::Bson::Null
}
}
"InputDateTime" => {
let mut val: String = value.1.clone();
if !val.is_empty() {
if !mango_orm::store::REGEX_IS_DATETIME.is_match(&val) {
panic!("Service: `{}` -> Model: `{}` -> \
Method: `widgets()` : \
Incorrect date and time format. \
Example: 1970-02-28T00:00",
meta.service, MODEL_NAME)
}
let dt: chrono::DateTime<chrono::Utc> =
chrono::DateTime::<chrono::Utc>::from_utc(
chrono::NaiveDateTime::parse_from_str(
&val, "%Y-%m-%dT%H:%M").unwrap(), chrono::Utc);
mongodb::bson::Bson::DateTime(dt)
} else {
mongodb::bson::Bson::Null
}
}
"InputCheckBoxI32" | "InputRadioI32" | "InputNumberI32"
| "InputRangeI32" | "SelectI32" => {
mongodb::bson::Bson::Int32(value.1.parse::<i32>().unwrap())
}
"InputCheckBoxU32" | "InputRadioU32" | "InputNumberU32"
| "InputRangeU32" | "SelectU32" | "InputCheckBoxI64"
| "InputRadioI64" | "InputNumberI64" | "InputRangeI64"
| "SelectI64" => {
mongodb::bson::Bson::Int64(value.1.parse::<i64>().unwrap())
}
"InputCheckBoxF64" | "InputRadioF64" | "InputNumberF64"
| "InputRangeF64" | "SelectF64" => {
mongodb::bson::Bson::Double(value.1.parse::<f64>().unwrap())
}
"InputCheckBoxBool" => {
mongodb::bson::Bson::Boolean(value.1.parse::<bool>().unwrap())
}
_ => {
panic!("Service: `{}` -> Model: `{}` -> Method: \
`migrat()` : Invalid enum type.",
meta.service, MODEL_NAME)
}
});
}
}
for field in vec!["created", "updated"] {
if doc_from_db.contains_key(field) {
let value_from_db: Option<&mongodb::bson::Bson> = doc_from_db.get(field);
if value_from_db.is_some() {
tmp_doc.insert(field.to_string(), value_from_db.unwrap());
} else {
panic!("Service: `{}` -> Model: `{}` -> \
Method: `migrat()` : \
Cannot get field value from database for \
field `{}`.",
meta.service, MODEL_NAME, field);
}
} else {
panic!("Service: `{}` -> Model: `{}` -> Method: `migrat()` : \
Key `{}` was not found in the document from \
the database.",
meta.service, MODEL_NAME, field);
}
}
let query = mongodb::bson::doc! {"_id": doc_from_db.get_object_id("_id").unwrap()};
let update = mongodb::options::UpdateModifications::Document(tmp_doc);
collection.update_one(query, update, None).await.unwrap();
}
}
}
let db: mongodb::Database = client.database(&meta.database);
if !database_names.contains(&meta.database) ||
!db.list_collection_names(None).await.unwrap().contains(&meta.collection) {
db.create_collection(&meta.collection, None).await.unwrap();
}
let db: mongodb::Database = client.database(&mango_orm_keyword);
if !database_names.contains(&mango_orm_keyword) ||
!db.list_collection_names(None).await.unwrap().contains(&"models".to_owned()) {
panic!("For migration not used `models::Monitor.refresh()`.");
} else {
let collection = db.collection("models");
let filter = mongodb::bson::doc! {"database": &meta.database, "collection": &meta.collection};
let doc = mongodb::bson::doc!{
"database": &meta.database,
"collection": &meta.collection,
"fields": FIELD_NAMES.iter().map(|field| field.to_string())
.filter(|field| field != "hash"
&& !ignore_fields.contains(&field.as_str()))
.collect::<Vec<String>>(),
"status": true
};
if collection.count_documents(filter.clone(), None).await.unwrap() == 0_i64 {
collection.insert_one(doc, None).await.unwrap();
} else {
let update = mongodb::options::UpdateModifications::Document(doc);
collection.update_one(filter, update, None).await.unwrap();
}
}
}
}
}
}