implement basic error handling
This commit is contained in:
parent
38792b0fb5
commit
4d2d625d25
@ -78,7 +78,7 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
|
|||||||
values: hashmap![
|
values: hashmap![
|
||||||
#(#fields_for_from_model),*
|
#(#fields_for_from_model),*
|
||||||
],
|
],
|
||||||
errors: Vec::new()
|
errors: HashMap::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -103,7 +103,8 @@ 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);
|
let mut validation_errs = Entity::validate_model(&model);
|
||||||
model.errors.append(&mut validation_errs);
|
//model.errors.append(&mut validation_errs);
|
||||||
|
model.errors = validation_errs;
|
||||||
|
|
||||||
if !model.has_errors() {
|
if !model.has_errors() {
|
||||||
let new_model = ActiveModel::from(model.clone());
|
let new_model = ActiveModel::from(model.clone());
|
||||||
@ -123,7 +124,8 @@ 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 {
|
||||||
let mut validation_errs = Entity::validate_model(&model);
|
let mut validation_errs = Entity::validate_model(&model);
|
||||||
model.errors.append(&mut validation_errs);
|
//model.errors.append(&mut validation_errs);
|
||||||
|
model.errors=validation_errs;
|
||||||
|
|
||||||
if !model.has_errors() {
|
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();
|
||||||
@ -182,13 +184,13 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn validate_model(model: &ActixAdminModel) -> Vec<ActixAdminError> {
|
fn validate_model(model: &ActixAdminModel) -> HashMap<String, String> {
|
||||||
let mut errors = Vec::<ActixAdminError>::new();
|
let mut errors = HashMap::<String, String>::new();
|
||||||
|
|
||||||
#(#fields_for_validate_model);*;
|
#(#fields_for_validate_model);*;
|
||||||
|
|
||||||
let mut custom_errors = Entity.validate();
|
//let mut custom_errors = Entity.validate();
|
||||||
errors.append(&mut custom_errors);
|
//errors.append(&mut custom_errors);
|
||||||
errors
|
errors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,12 +221,12 @@ pub fn get_fields_for_validate_model(fields: &Vec<ModelField>) -> Vec<TokenStrea
|
|||||||
true => {
|
true => {
|
||||||
let inner_ty = model_field.inner_type.to_owned().unwrap();
|
let inner_ty = model_field.inner_type.to_owned().unwrap();
|
||||||
quote! {
|
quote! {
|
||||||
model.get_value::<#inner_ty>(#ident_name).map_err(|err| errors.push(err)).ok();
|
model.get_value::<#inner_ty>(#ident_name).map_err(|err| errors.insert(#ident_name.to_string(), err)).ok();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
false => {
|
false => {
|
||||||
quote! {
|
quote! {
|
||||||
model.get_value::<#ty>(#ident_name).map_err(|err| errors.push(err)).ok();
|
model.get_value::<#ty>(#ident_name).map_err(|err| errors.insert(#ident_name.to_string(), err)).ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,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, ActixAdminError };
|
pub use crate::{ ActixAdminAppDataTrait, ActixAdmin };
|
||||||
pub use crate::{ hashmap, ActixAdminSelectListTrait };
|
pub use crate::{ hashmap, ActixAdminSelectListTrait };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,12 +35,6 @@ 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;
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
|
use crate::ActixAdminViewModelField;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use sea_orm::DatabaseConnection;
|
use sea_orm::DatabaseConnection;
|
||||||
use serde::{Serialize};
|
use serde::Serialize;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use crate::ActixAdminViewModelField;
|
|
||||||
use crate::ActixAdminError;
|
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait ActixAdminModelTrait {
|
pub trait ActixAdminModelTrait {
|
||||||
@ -13,11 +12,10 @@ 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>;
|
fn validate_model(model: &ActixAdminModel) -> HashMap<String, String>;
|
||||||
|
|
||||||
// function to be overridable for custom error handling
|
// function to be overridable for custom error handling
|
||||||
fn validate(&self) -> Vec<ActixAdminError> {
|
fn validate(&self) -> HashMap<String, String> {
|
||||||
return Vec::new()
|
return HashMap::new();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,7 +23,7 @@ pub trait ActixAdminModelTrait {
|
|||||||
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>,
|
pub errors: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<String> for ActixAdminModel {
|
impl From<String> for ActixAdminModel {
|
||||||
@ -33,38 +31,49 @@ impl From<String> for ActixAdminModel {
|
|||||||
let mut hashmap = HashMap::new();
|
let mut hashmap = HashMap::new();
|
||||||
let key_values: Vec<&str> = string.split('&').collect();
|
let key_values: Vec<&str> = string.split('&').collect();
|
||||||
for key_value in key_values {
|
for key_value in key_values {
|
||||||
let mut iter = key_value.splitn(2, '=');
|
if !key_value.is_empty() {
|
||||||
hashmap.insert(
|
let mut iter = key_value.splitn(2, '=');
|
||||||
iter.next().unwrap().to_string(),
|
hashmap.insert(
|
||||||
iter.next().unwrap().to_string(),
|
iter.next().unwrap().to_string(),
|
||||||
);
|
iter.next().unwrap().to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ActixAdminModel { primary_key: None, values: hashmap, errors: Vec::new() }
|
ActixAdminModel {
|
||||||
|
primary_key: None,
|
||||||
|
values: hashmap,
|
||||||
|
errors: HashMap::new(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ActixAdminModel {
|
impl ActixAdminModel {
|
||||||
pub fn get_value<T: std::str::FromStr>(&self, key: &str) -> Result<Option<T>, ActixAdminError> {
|
pub fn get_value<T: std::str::FromStr>(&self, key: &str) -> Result<Option<T>, String> {
|
||||||
let value = self.values.get(key);
|
let value = self.values.get(key);
|
||||||
let res: Result<Option<T>, ActixAdminError> = match value {
|
println!("{:?}", key);
|
||||||
|
println!("{:?}", value);
|
||||||
|
|
||||||
|
let res: Result<Option<T>, String> = match value {
|
||||||
Some(val) => {
|
Some(val) => {
|
||||||
let parsed_val = val.parse::<T>();
|
if val.is_empty() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let parsed_val = val.parse::<T>();
|
||||||
|
|
||||||
match parsed_val {
|
match parsed_val {
|
||||||
Ok(val) => Ok(Some(val)),
|
Ok(val) => Ok(Some(val)),
|
||||||
Err(_) => Err(ActixAdminError {
|
Err(_) => Err("Invalid Value".to_string()),
|
||||||
field_name: Some(key.to_string()),
|
|
||||||
error: "Invalid Value".to_string()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
_ => Ok(None)
|
_ => Ok(None),
|
||||||
};
|
};
|
||||||
|
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_errors(&self) -> bool {
|
pub fn has_errors(&self) -> bool {
|
||||||
return &self.errors.len() != &0
|
return &self.errors.len() != &0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,24 +9,26 @@ pub async fn create_get<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
|||||||
_req: HttpRequest,
|
_req: HttpRequest,
|
||||||
data: web::Data<T>,
|
data: web::Data<T>,
|
||||||
_body: web::Payload,
|
_body: web::Payload,
|
||||||
_text: String,
|
text: String,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
let _db = &data.get_db();
|
let db = &data.get_db();
|
||||||
let entity_name = E::get_entity_name();
|
let entity_name = E::get_entity_name();
|
||||||
let entity_names = &data.get_actix_admin().entity_names;
|
let entity_names = &data.get_actix_admin().entity_names;
|
||||||
|
|
||||||
let actix_admin = data.get_actix_admin();
|
let actix_admin = data.get_actix_admin();
|
||||||
|
|
||||||
let view_model = actix_admin.view_models.get(&entity_name).unwrap();
|
let view_model = actix_admin.view_models.get(&entity_name).unwrap();
|
||||||
|
let mut model = ActixAdminModel::from(text);
|
||||||
|
|
||||||
let mut ctx = Context::new();
|
let mut ctx = Context::new();
|
||||||
ctx.insert("entity_names", &entity_names);
|
ctx.insert("entity_names", &entity_names);
|
||||||
ctx.insert("view_model", &view_model);
|
ctx.insert("view_model", &view_model);
|
||||||
ctx.insert("select_lists", &E::get_select_lists(_db).await);
|
ctx.insert("select_lists", &E::get_select_lists(db).await);
|
||||||
ctx.insert("list_link", &E::get_list_link(&entity_name));
|
ctx.insert("list_link", &E::get_list_link(&entity_name));
|
||||||
|
ctx.insert("model", &model);
|
||||||
|
|
||||||
let body = TERA
|
let body = TERA
|
||||||
.render("create.html", &ctx)
|
.render("edit.html", &ctx)
|
||||||
.map_err(|err| error::ErrorInternalServerError(err))?;
|
.map_err(|err| error::ErrorInternalServerError(err))?;
|
||||||
Ok(HttpResponse::Ok().content_type("text/html").body(body))
|
Ok(HttpResponse::Ok().content_type("text/html").body(body))
|
||||||
}
|
}
|
@ -1,5 +1,7 @@
|
|||||||
use actix_web::http::header;
|
use actix_web::http::header;
|
||||||
use actix_web::{web, Error, HttpRequest, HttpResponse};
|
use actix_web::{web, error, Error, HttpRequest, HttpResponse};
|
||||||
|
use tera::{Context};
|
||||||
|
use crate::TERA;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
@ -10,15 +12,32 @@ pub async fn create_post<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>
|
|||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
let db = &data.get_db();
|
let db = &data.get_db();
|
||||||
let entity_name = E::get_entity_name();
|
let entity_name = E::get_entity_name();
|
||||||
|
let entity_names = &data.get_actix_admin().entity_names;
|
||||||
let actix_admin = data.get_actix_admin();
|
let actix_admin = data.get_actix_admin();
|
||||||
let view_model = actix_admin.view_models.get(&entity_name).unwrap();
|
let view_model = actix_admin.view_models.get(&entity_name).unwrap();
|
||||||
let mut _admin_model = ActixAdminModel::from(text);
|
let mut model = ActixAdminModel::from(text);
|
||||||
_admin_model = E::create_entity(db, _admin_model).await;
|
model = E::create_entity(db, model).await;
|
||||||
|
|
||||||
Ok(HttpResponse::Found()
|
if model.has_errors() {
|
||||||
|
let mut ctx = Context::new();
|
||||||
|
ctx.insert("entity_names", &entity_names);
|
||||||
|
ctx.insert("view_model", &view_model);
|
||||||
|
ctx.insert("select_lists", &E::get_select_lists(db).await);
|
||||||
|
ctx.insert("list_link", &E::get_list_link(&entity_name));
|
||||||
|
ctx.insert("model", &model);
|
||||||
|
println!("{:?}", model.errors);
|
||||||
|
|
||||||
|
let body = TERA
|
||||||
|
.render("edit.html", &ctx)
|
||||||
|
.map_err(|err| error::ErrorInternalServerError(err))?;
|
||||||
|
Ok(HttpResponse::Ok().content_type("text/html").body(body))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Ok(HttpResponse::Found()
|
||||||
.append_header((
|
.append_header((
|
||||||
header::LOCATION,
|
header::LOCATION,
|
||||||
format!("/admin/{}/list", view_model.entity_name),
|
format!("/admin/{}/list", view_model.entity_name),
|
||||||
))
|
))
|
||||||
.finish())
|
.finish())
|
||||||
|
}
|
||||||
}
|
}
|
@ -8,7 +8,6 @@ use crate::TERA;
|
|||||||
pub async fn edit_get<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
pub async fn edit_get<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
||||||
_req: HttpRequest,
|
_req: HttpRequest,
|
||||||
data: web::Data<T>,
|
data: web::Data<T>,
|
||||||
_body: web::Payload,
|
|
||||||
_text: String,
|
_text: String,
|
||||||
id: web::Path<i32>
|
id: web::Path<i32>
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
use actix_web::http::header;
|
use actix_web::http::header;
|
||||||
use actix_web::{web, Error, HttpRequest, HttpResponse};
|
use actix_web::{web, error, Error, HttpRequest, HttpResponse};
|
||||||
|
use tera::{Context};
|
||||||
|
use crate::TERA;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use super::edit_get::edit_get;
|
||||||
|
|
||||||
pub async fn edit_post<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
pub async fn edit_post<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
||||||
_req: HttpRequest,
|
_req: HttpRequest,
|
||||||
@ -12,14 +15,30 @@ pub async fn edit_post<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
|||||||
let db = &data.get_db();
|
let db = &data.get_db();
|
||||||
let entity_name = E::get_entity_name();
|
let entity_name = E::get_entity_name();
|
||||||
let actix_admin = data.get_actix_admin();
|
let actix_admin = data.get_actix_admin();
|
||||||
|
let entity_names = &data.get_actix_admin().entity_names;
|
||||||
let view_model = actix_admin.view_models.get(&entity_name).unwrap();
|
let view_model = actix_admin.view_models.get(&entity_name).unwrap();
|
||||||
let mut _admin_model = ActixAdminModel::from(text);
|
let mut model = ActixAdminModel::from(text);
|
||||||
_admin_model = E::edit_entity(db, id.into_inner(), _admin_model).await;
|
model = E::edit_entity(db, id.into_inner(), model).await;
|
||||||
|
|
||||||
|
if model.has_errors() {
|
||||||
|
let mut ctx = Context::new();
|
||||||
|
ctx.insert("entity_names", &entity_names);
|
||||||
|
ctx.insert("view_model", &view_model);
|
||||||
|
ctx.insert("model", &model);
|
||||||
|
ctx.insert("select_lists", &E::get_select_lists(db).await);
|
||||||
|
ctx.insert("list_link", &E::get_list_link(&entity_name));
|
||||||
|
|
||||||
|
let body = TERA
|
||||||
|
.render("edit.html", &ctx)
|
||||||
|
.map_err(|err| error::ErrorInternalServerError(err))?;
|
||||||
|
Ok(HttpResponse::Ok().content_type("text/html").body(body))
|
||||||
|
}
|
||||||
|
else {
|
||||||
Ok(HttpResponse::Found()
|
Ok(HttpResponse::Found()
|
||||||
.append_header((
|
.append_header((
|
||||||
header::LOCATION,
|
header::LOCATION,
|
||||||
format!("/admin/{}/list", view_model.entity_name),
|
format!("/admin/{}/list", view_model.entity_name),
|
||||||
))
|
))
|
||||||
.finish())
|
.finish())
|
||||||
|
}
|
||||||
}
|
}
|
@ -2,26 +2,38 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<article>
|
<article>
|
||||||
|
<div>
|
||||||
|
<ul>
|
||||||
|
{{ model.errors }}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
<form method="post">
|
<form method="post">
|
||||||
<div>
|
<div>
|
||||||
{% for model_field in view_model.fields -%}
|
{% for model_field in view_model.fields -%}
|
||||||
<label for="{{ model_field.field_name }}">
|
<label for="{{ model_field.field_name }}">
|
||||||
{{ model_field.field_name }}
|
{{ model_field.field_name }}
|
||||||
{% if model_field.select_list != "" %}
|
{% if model_field.select_list != "" %}
|
||||||
<select name="{{ model_field.field_name }}">
|
<select name="{{ model_field.field_name }}"
|
||||||
|
{% if model.errors | get(key=model_field.field_name, default="" ) !="" %} placeholder="Invalid" aria-invalid="true" {% endif %}>
|
||||||
{% if model_field.is_option %}
|
{% if model_field.is_option %}
|
||||||
<option value=""></option>
|
<option value=""></option>
|
||||||
|
{% else %}
|
||||||
|
<option value="" selected disabled>Select</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for select_list_item in select_lists[model_field.field_name] -%}
|
{% for select_list_item in select_lists[model_field.field_name] -%}
|
||||||
<option {% if select_list_item[0]==model.values | get(key=model_field.field_name) %} selected {%
|
<option {% if select_list_item[0]==model.values | get(key=model_field.field_name, default="" ) %}
|
||||||
endif %} value="{{ select_list_item[0] }}">{{ select_list_item[1] }}</option>
|
selected {% endif %} value="{{ select_list_item[0] }}">{{ select_list_item[1] }}</option>
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
{% else %}
|
{% else %}
|
||||||
<input type="{{ model_field.html_input_type }}" value="{{ model.values | get(key=model_field.field_name) }}"
|
<input type="{{ model_field.html_input_type }}"
|
||||||
|
value="{{ model.values | get(key=model_field.field_name, default="") }}"
|
||||||
name="{{ model_field.field_name }}" placeholder="{{ model_field.field_name }}"
|
name="{{ model_field.field_name }}" placeholder="{{ model_field.field_name }}"
|
||||||
aria-label="{{ model_field.field_name }}"><!-- required="" -->
|
aria-label="{{ model_field.field_name }}">
|
||||||
|
{% if model.errors | get(key=model_field.field_name, default="") != "" %}
|
||||||
|
placeholder="Invalid" aria-invalid="true"
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</label>
|
</label>
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
|
BIN
database.db-wal
BIN
database.db-wal
Binary file not shown.
Loading…
Reference in New Issue
Block a user