add error handling in case of model errors

This commit is contained in:
Manuel Gugger 2022-07-26 17:47:18 +02:00
parent 3702474b41
commit 130a2b4098
6 changed files with 102 additions and 24 deletions

View File

@ -12,7 +12,8 @@ use struct_fields::{
get_field_for_primary_key, get_field_for_primary_key,
get_primary_key_field_name, get_primary_key_field_name,
get_actix_admin_fields_select_list, 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; 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_edit_model = get_fields_for_edit_model(&fields);
let fields_for_from_model = get_fields_for_from_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 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); 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, #field_for_primary_key,
values: hashmap![ values: hashmap![
#(#fields_for_from_model),* #(#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 { async fn create_entity(db: &DatabaseConnection, mut model: ActixAdminModel) -> ActixAdminModel {
let new_model = ActiveModel::from(model.clone()); let mut validation_errs = Entity::validate_model(&model);
let insert_operation = Entity::insert(new_model).exec(db).await; 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 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 { 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);
let entity: Option<Model> = Entity::find_by_id(id).one(db).await.unwrap(); model.errors.append(&mut validation_errs);
let mut entity: ActiveModel = entity.unwrap().into();
#(#fields_for_edit_model);*; if !model.has_errors() {
let entity: Option<Model> = Entity::find_by_id(id).one(db).await.unwrap();
let mut entity: ActiveModel = entity.unwrap().into();
let entity: Model = entity.update(db).await.unwrap(); #(#fields_for_edit_model);*;
let entity: Model = entity.update(db).await.unwrap();
}
model model
} }
@ -169,6 +181,17 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
(num_pages, model_entities) (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> { fn get_fields() -> Vec<ActixAdminViewModelField> {
let mut vec = Vec::new(); let mut vec = Vec::new();

View File

@ -208,6 +208,32 @@ pub fn get_fields_for_from_model(fields: &Vec<ModelField>) -> Vec<TokenStream> {
.collect::<Vec<_>>() .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> { pub fn get_fields_for_create_model(fields: &Vec<ModelField>) -> Vec<TokenStream> {
fields fields
.iter() .iter()
@ -222,12 +248,12 @@ pub fn get_fields_for_create_model(fields: &Vec<ModelField>) -> Vec<TokenStream>
true => { true => {
let inner_ty = model_field.inner_type.to_owned().unwrap(); let inner_ty = model_field.inner_type.to_owned().unwrap();
quote! { quote! {
#ident: Set(model.get_value::<#inner_ty>(#ident_name)) #ident: Set(model.get_value::<#inner_ty>(#ident_name).unwrap())
} }
}, },
false => { false => {
quote! { 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 => { true => {
let inner_ty = model_field.inner_type.to_owned().unwrap(); let inner_ty = model_field.inner_type.to_owned().unwrap();
quote! { quote! {
entity.#ident = Set(model.get_value::<#inner_ty>(#ident_name)) entity.#ident = Set(model.get_value::<#inner_ty>(#ident_name).unwrap())
} }
}, },
false => { false => {
quote! { quote! {
entity.#ident = Set(model.get_value::<#ty>(#ident_name).unwrap()) entity.#ident = Set(model.get_value::<#ty>(#ident_name).unwrap().unwrap())
} }
} }
} }

View File

@ -2,6 +2,7 @@ use lazy_static::lazy_static;
use sea_orm::DatabaseConnection; use sea_orm::DatabaseConnection;
use std::collections::HashMap; use std::collections::HashMap;
use tera::{Tera}; use tera::{Tera};
use serde::{Serialize};
pub mod view_model; pub mod view_model;
pub mod model; pub mod model;
@ -13,7 +14,7 @@ pub mod prelude {
pub use crate::model::{ ActixAdminModel, ActixAdminModelTrait}; pub use crate::model::{ ActixAdminModel, ActixAdminModelTrait};
pub use crate::view_model::{ ActixAdminViewModel, ActixAdminViewModelTrait, ActixAdminViewModelField}; pub use crate::view_model::{ ActixAdminViewModel, ActixAdminViewModelTrait, ActixAdminViewModelField};
pub use actix_admin_macros::{ DeriveActixAdminModel, DeriveActixAdminSelectList }; pub use actix_admin_macros::{ DeriveActixAdminModel, DeriveActixAdminSelectList };
pub use crate::{ ActixAdminAppDataTrait, ActixAdmin}; pub use crate::{ ActixAdminAppDataTrait, ActixAdmin, ActixAdminError };
pub use crate::{ hashmap, ActixAdminSelectListTrait }; pub use crate::{ hashmap, ActixAdminSelectListTrait };
} }
@ -34,6 +35,12 @@ lazy_static! {
Tera::new(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*")).unwrap(); Tera::new(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*")).unwrap();
} }
#[derive(Clone, Debug, Serialize)]
pub struct ActixAdminError {
field_name: Option<String>,
error: String
}
// AppDataTrait // AppDataTrait
pub trait ActixAdminAppDataTrait { pub trait ActixAdminAppDataTrait {
fn get_db(&self) -> &DatabaseConnection; fn get_db(&self) -> &DatabaseConnection;

View File

@ -3,6 +3,7 @@ use sea_orm::DatabaseConnection;
use serde::{Serialize}; use serde::{Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use crate::ActixAdminViewModelField; use crate::ActixAdminViewModelField;
use crate::ActixAdminError;
#[async_trait] #[async_trait]
pub trait ActixAdminModelTrait { pub trait ActixAdminModelTrait {
@ -12,12 +13,19 @@ pub trait ActixAdminModelTrait {
posts_per_page: usize, posts_per_page: usize,
) -> (usize, Vec<ActixAdminModel>); ) -> (usize, Vec<ActixAdminModel>);
fn get_fields() -> Vec<ActixAdminViewModelField>; 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)] #[derive(Clone, Debug, Serialize)]
pub struct ActixAdminModel { pub struct ActixAdminModel {
pub primary_key: Option<String>, pub primary_key: Option<String>,
pub values: HashMap<String, String>, pub values: HashMap<String, String>,
pub errors: Vec<ActixAdminError>,
} }
impl From<String> for ActixAdminModel { 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 { impl ActixAdminModel {
pub fn get_value<T: std::str::FromStr>(&self, key: &str) -> Option<T> { pub fn get_value<T: std::str::FromStr>(&self, key: &str) -> Result<Option<T>, ActixAdminError> {
println!("{:?}", self.values); let value = self.values.get(key);
let value = self.values.get(key).unwrap().to_string().parse::<T>(); let res: Result<Option<T>, ActixAdminError> = match value {
match value { Some(val) => {
Ok(val) => Some(val), let parsed_val = val.parse::<T>();
Err(_) => None, //panic!("key {} could not be parsed", key) 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
} }
} }

View File

@ -24,7 +24,7 @@
{%- endfor %} {%- endfor %}
<td> <td>
<a href="edit/{{ entity.primary_key }}">&#9998;</a> <a href="edit/{{ entity.primary_key }}">&#9998;</a>
<a hx-post="delete/{{ entity.primary_key }}">&#128465;</a> <a href="#" hx-post="delete/{{ entity.primary_key }}">&#128465;</a>
</td> </td>
</tr> </tr>
{%- endfor %} {%- endfor %}

Binary file not shown.