add error handling in case of model errors
This commit is contained in:
parent
3702474b41
commit
130a2b4098
@ -12,7 +12,8 @@ use struct_fields::{
|
||||
get_field_for_primary_key,
|
||||
get_primary_key_field_name,
|
||||
get_actix_admin_fields_select_list,
|
||||
get_actix_admin_fields_is_option_list
|
||||
get_actix_admin_fields_is_option_list,
|
||||
get_fields_for_validate_model
|
||||
};
|
||||
|
||||
mod selectlist_fields;
|
||||
@ -42,6 +43,7 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
|
||||
let fields_for_edit_model = get_fields_for_edit_model(&fields);
|
||||
let fields_for_from_model = get_fields_for_from_model(&fields);
|
||||
let field_for_primary_key = get_field_for_primary_key(&fields);
|
||||
let fields_for_validate_model = get_fields_for_validate_model(&fields);
|
||||
|
||||
let select_lists = get_select_lists(&fields);
|
||||
|
||||
@ -75,7 +77,8 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
|
||||
#field_for_primary_key,
|
||||
values: hashmap![
|
||||
#(#fields_for_from_model),*
|
||||
]
|
||||
],
|
||||
errors: Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -99,8 +102,13 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
|
||||
}
|
||||
|
||||
async fn create_entity(db: &DatabaseConnection, mut model: ActixAdminModel) -> ActixAdminModel {
|
||||
let mut validation_errs = Entity::validate_model(&model);
|
||||
model.errors.append(&mut validation_errs);
|
||||
|
||||
if !model.has_errors() {
|
||||
let new_model = ActiveModel::from(model.clone());
|
||||
let insert_operation = Entity::insert(new_model).exec(db).await;
|
||||
}
|
||||
|
||||
model
|
||||
}
|
||||
@ -114,13 +122,17 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
|
||||
}
|
||||
|
||||
async fn edit_entity(db: &DatabaseConnection, id: i32, mut model: ActixAdminModel) -> ActixAdminModel {
|
||||
// TODO: separate primary key from other keys
|
||||
let mut validation_errs = Entity::validate_model(&model);
|
||||
model.errors.append(&mut validation_errs);
|
||||
|
||||
if !model.has_errors() {
|
||||
let entity: Option<Model> = Entity::find_by_id(id).one(db).await.unwrap();
|
||||
let mut entity: ActiveModel = entity.unwrap().into();
|
||||
|
||||
#(#fields_for_edit_model);*;
|
||||
|
||||
let entity: Model = entity.update(db).await.unwrap();
|
||||
}
|
||||
|
||||
model
|
||||
}
|
||||
@ -169,6 +181,17 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
|
||||
(num_pages, model_entities)
|
||||
}
|
||||
|
||||
|
||||
fn validate_model(model: &ActixAdminModel) -> Vec<ActixAdminError> {
|
||||
let mut errors = Vec::<ActixAdminError>::new();
|
||||
|
||||
#(#fields_for_validate_model);*;
|
||||
|
||||
let mut custom_errors = Entity.validate();
|
||||
errors.append(&mut custom_errors);
|
||||
errors
|
||||
}
|
||||
|
||||
fn get_fields() -> Vec<ActixAdminViewModelField> {
|
||||
let mut vec = Vec::new();
|
||||
|
||||
|
@ -208,6 +208,32 @@ pub fn get_fields_for_from_model(fields: &Vec<ModelField>) -> Vec<TokenStream> {
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
pub fn get_fields_for_validate_model(fields: &Vec<ModelField>) -> Vec<TokenStream> {
|
||||
fields
|
||||
.iter()
|
||||
// TODO: filter id attr based on struct attr or sea_orm primary_key attr
|
||||
.filter(|model_field| !model_field.primary_key)
|
||||
.map(|model_field| {
|
||||
let ident_name = model_field.ident.to_string();
|
||||
let ty = model_field.ty.to_owned();
|
||||
|
||||
match model_field.is_option() {
|
||||
true => {
|
||||
let inner_ty = model_field.inner_type.to_owned().unwrap();
|
||||
quote! {
|
||||
model.get_value::<#inner_ty>(#ident_name).map_err(|err| errors.push(err)).ok();
|
||||
}
|
||||
},
|
||||
false => {
|
||||
quote! {
|
||||
model.get_value::<#ty>(#ident_name).map_err(|err| errors.push(err)).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
pub fn get_fields_for_create_model(fields: &Vec<ModelField>) -> Vec<TokenStream> {
|
||||
fields
|
||||
.iter()
|
||||
@ -222,12 +248,12 @@ pub fn get_fields_for_create_model(fields: &Vec<ModelField>) -> Vec<TokenStream>
|
||||
true => {
|
||||
let inner_ty = model_field.inner_type.to_owned().unwrap();
|
||||
quote! {
|
||||
#ident: Set(model.get_value::<#inner_ty>(#ident_name))
|
||||
#ident: Set(model.get_value::<#inner_ty>(#ident_name).unwrap())
|
||||
}
|
||||
},
|
||||
false => {
|
||||
quote! {
|
||||
#ident: Set(model.get_value::<#ty>(#ident_name).unwrap())
|
||||
#ident: Set(model.get_value::<#ty>(#ident_name).unwrap().unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -249,12 +275,12 @@ pub fn get_fields_for_edit_model(fields: &Vec<ModelField>) -> Vec<TokenStream> {
|
||||
true => {
|
||||
let inner_ty = model_field.inner_type.to_owned().unwrap();
|
||||
quote! {
|
||||
entity.#ident = Set(model.get_value::<#inner_ty>(#ident_name))
|
||||
entity.#ident = Set(model.get_value::<#inner_ty>(#ident_name).unwrap())
|
||||
}
|
||||
},
|
||||
false => {
|
||||
quote! {
|
||||
entity.#ident = Set(model.get_value::<#ty>(#ident_name).unwrap())
|
||||
entity.#ident = Set(model.get_value::<#ty>(#ident_name).unwrap().unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ use lazy_static::lazy_static;
|
||||
use sea_orm::DatabaseConnection;
|
||||
use std::collections::HashMap;
|
||||
use tera::{Tera};
|
||||
use serde::{Serialize};
|
||||
|
||||
pub mod view_model;
|
||||
pub mod model;
|
||||
@ -13,7 +14,7 @@ pub mod prelude {
|
||||
pub use crate::model::{ ActixAdminModel, ActixAdminModelTrait};
|
||||
pub use crate::view_model::{ ActixAdminViewModel, ActixAdminViewModelTrait, ActixAdminViewModelField};
|
||||
pub use actix_admin_macros::{ DeriveActixAdminModel, DeriveActixAdminSelectList };
|
||||
pub use crate::{ ActixAdminAppDataTrait, ActixAdmin};
|
||||
pub use crate::{ ActixAdminAppDataTrait, ActixAdmin, ActixAdminError };
|
||||
pub use crate::{ hashmap, ActixAdminSelectListTrait };
|
||||
}
|
||||
|
||||
@ -34,6 +35,12 @@ lazy_static! {
|
||||
Tera::new(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*")).unwrap();
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct ActixAdminError {
|
||||
field_name: Option<String>,
|
||||
error: String
|
||||
}
|
||||
|
||||
// AppDataTrait
|
||||
pub trait ActixAdminAppDataTrait {
|
||||
fn get_db(&self) -> &DatabaseConnection;
|
||||
|
@ -3,6 +3,7 @@ use sea_orm::DatabaseConnection;
|
||||
use serde::{Serialize};
|
||||
use std::collections::HashMap;
|
||||
use crate::ActixAdminViewModelField;
|
||||
use crate::ActixAdminError;
|
||||
|
||||
#[async_trait]
|
||||
pub trait ActixAdminModelTrait {
|
||||
@ -12,12 +13,19 @@ pub trait ActixAdminModelTrait {
|
||||
posts_per_page: usize,
|
||||
) -> (usize, Vec<ActixAdminModel>);
|
||||
fn get_fields() -> Vec<ActixAdminViewModelField>;
|
||||
fn validate_model(model: &ActixAdminModel) -> Vec<ActixAdminError>;
|
||||
|
||||
// function to be overridable for custom error handling
|
||||
fn validate(&self) -> Vec<ActixAdminError> {
|
||||
return Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct ActixAdminModel {
|
||||
pub primary_key: Option<String>,
|
||||
pub values: HashMap<String, String>,
|
||||
pub errors: Vec<ActixAdminError>,
|
||||
}
|
||||
|
||||
impl From<String> for ActixAdminModel {
|
||||
@ -32,17 +40,31 @@ impl From<String> for ActixAdminModel {
|
||||
);
|
||||
}
|
||||
|
||||
ActixAdminModel { primary_key: None, values: hashmap }
|
||||
ActixAdminModel { primary_key: None, values: hashmap, errors: Vec::new() }
|
||||
}
|
||||
}
|
||||
|
||||
impl ActixAdminModel {
|
||||
pub fn get_value<T: std::str::FromStr>(&self, key: &str) -> Option<T> {
|
||||
println!("{:?}", self.values);
|
||||
let value = self.values.get(key).unwrap().to_string().parse::<T>();
|
||||
match value {
|
||||
Ok(val) => Some(val),
|
||||
Err(_) => None, //panic!("key {} could not be parsed", key)
|
||||
}
|
||||
pub fn get_value<T: std::str::FromStr>(&self, key: &str) -> Result<Option<T>, ActixAdminError> {
|
||||
let value = self.values.get(key);
|
||||
let res: Result<Option<T>, ActixAdminError> = match value {
|
||||
Some(val) => {
|
||||
let parsed_val = val.parse::<T>();
|
||||
match parsed_val {
|
||||
Ok(val) => Ok(Some(val)),
|
||||
Err(_) => Err(ActixAdminError {
|
||||
field_name: Some(key.to_string()),
|
||||
error: "Invalid Value".to_string()
|
||||
})
|
||||
}
|
||||
},
|
||||
_ => Ok(None)
|
||||
};
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
pub fn has_errors(&self) -> bool {
|
||||
return &self.errors.len() != &0
|
||||
}
|
||||
}
|
@ -24,7 +24,7 @@
|
||||
{%- endfor %}
|
||||
<td>
|
||||
<a href="edit/{{ entity.primary_key }}">✎</a>
|
||||
<a hx-post="delete/{{ entity.primary_key }}">🗑</a>
|
||||
<a href="#" hx-post="delete/{{ entity.primary_key }}">🗑</a>
|
||||
</td>
|
||||
</tr>
|
||||
{%- endfor %}
|
||||
|
BIN
database.db-wal
BIN
database.db-wal
Binary file not shown.
Loading…
Reference in New Issue
Block a user