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_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 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 new_model = ActiveModel::from(model.clone());
|
||||||
let insert_operation = Entity::insert(new_model).exec(db).await;
|
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);
|
||||||
|
model.errors.append(&mut validation_errs);
|
||||||
|
|
||||||
|
if !model.has_errors() {
|
||||||
let entity: Option<Model> = Entity::find_by_id(id).one(db).await.unwrap();
|
let entity: Option<Model> = Entity::find_by_id(id).one(db).await.unwrap();
|
||||||
let mut entity: ActiveModel = entity.unwrap().into();
|
let mut entity: ActiveModel = entity.unwrap().into();
|
||||||
|
|
||||||
#(#fields_for_edit_model);*;
|
#(#fields_for_edit_model);*;
|
||||||
|
|
||||||
let entity: Model = entity.update(db).await.unwrap();
|
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();
|
||||||
|
|
||||||
|
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -24,7 +24,7 @@
|
|||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
<td>
|
<td>
|
||||||
<a href="edit/{{ entity.primary_key }}">✎</a>
|
<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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
|
BIN
database.db-wal
BIN
database.db-wal
Binary file not shown.
Loading…
Reference in New Issue
Block a user