implement field validation
This commit is contained in:
parent
4d2d625d25
commit
1f88d67b76
@ -1,6 +1,7 @@
|
|||||||
use syn::{
|
use syn::{
|
||||||
Visibility, Type
|
Visibility, Type
|
||||||
};
|
};
|
||||||
|
use quote::ToTokens;
|
||||||
|
|
||||||
pub struct ModelField {
|
pub struct ModelField {
|
||||||
pub visibility: Visibility,
|
pub visibility: Visibility,
|
||||||
@ -17,4 +18,13 @@ impl ModelField {
|
|||||||
pub fn is_option(&self) -> bool {
|
pub fn is_option(&self) -> bool {
|
||||||
self.inner_type.is_some()
|
self.inner_type.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_string(&self) -> bool {
|
||||||
|
match &self.ty {
|
||||||
|
Type::Path(type_path) if type_path.clone().into_token_stream().to_string() == "String" => {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,10 +1,8 @@
|
|||||||
use proc_macro2::{TokenStream};
|
|
||||||
use syn::{
|
|
||||||
Fields, DeriveInput, LitStr
|
|
||||||
};
|
|
||||||
use quote::quote;
|
|
||||||
use crate::attributes::derive_attr;
|
use crate::attributes::derive_attr;
|
||||||
use crate::model_fields::{ ModelField };
|
use crate::model_fields::ModelField;
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
|
use syn::{DeriveInput, Fields, LitStr};
|
||||||
|
|
||||||
pub fn get_fields_for_tokenstream(input: proc_macro::TokenStream) -> std::vec::Vec<ModelField> {
|
pub fn get_fields_for_tokenstream(input: proc_macro::TokenStream) -> std::vec::Vec<ModelField> {
|
||||||
let ast: DeriveInput = syn::parse(input).unwrap();
|
let ast: DeriveInput = syn::parse(input).unwrap();
|
||||||
@ -22,21 +20,28 @@ pub fn filter_fields(fields: &Fields) -> Vec<ModelField> {
|
|||||||
fields
|
fields
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|field| {
|
.filter_map(|field| {
|
||||||
let actix_admin_attr = derive_attr::ActixAdmin::try_from_attributes(&field.attrs).unwrap_or_default();
|
let actix_admin_attr =
|
||||||
|
derive_attr::ActixAdmin::try_from_attributes(&field.attrs).unwrap_or_default();
|
||||||
|
|
||||||
if field.ident.is_some()
|
if field.ident.is_some() {
|
||||||
{
|
|
||||||
let field_vis = field.vis.clone();
|
let field_vis = field.vis.clone();
|
||||||
let field_ident = field.ident.as_ref().unwrap().clone();
|
let field_ident = field.ident.as_ref().unwrap().clone();
|
||||||
let inner_type = extract_type_from_option(&field.ty);
|
let inner_type = extract_type_from_option(&field.ty);
|
||||||
let field_ty = field.ty.to_owned();
|
let field_ty = field.ty.to_owned();
|
||||||
let is_primary_key = actix_admin_attr.clone().map_or(false, |attr| attr.primary_key.is_some());
|
let is_primary_key = actix_admin_attr
|
||||||
let select_list = actix_admin_attr.clone().map_or("".to_string(), |attr| attr.select_list.map_or("".to_string(),
|
.clone()
|
||||||
|attr_field| (LitStr::from(attr_field)).value()
|
.map_or(false, |attr| attr.primary_key.is_some());
|
||||||
));
|
let select_list = actix_admin_attr.clone().map_or("".to_string(), |attr| {
|
||||||
let html_input_type = actix_admin_attr.map_or("text".to_string(), |attr| attr.html_input_type.map_or("text".to_string(),
|
attr.select_list.map_or("".to_string(), |attr_field| {
|
||||||
|attr_field| (LitStr::from(attr_field)).value()
|
(LitStr::from(attr_field)).value()
|
||||||
));
|
})
|
||||||
|
});
|
||||||
|
let html_input_type = actix_admin_attr.map_or("text".to_string(), |attr| {
|
||||||
|
attr.html_input_type
|
||||||
|
.map_or("text".to_string(), |attr_field| {
|
||||||
|
(LitStr::from(attr_field)).value()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
let model_field = ModelField {
|
let model_field = ModelField {
|
||||||
visibility: field_vis,
|
visibility: field_vis,
|
||||||
@ -45,9 +50,8 @@ pub fn filter_fields(fields: &Fields) -> Vec<ModelField> {
|
|||||||
inner_type: inner_type,
|
inner_type: inner_type,
|
||||||
primary_key: is_primary_key,
|
primary_key: is_primary_key,
|
||||||
html_input_type: html_input_type,
|
html_input_type: html_input_type,
|
||||||
select_list: select_list
|
select_list: select_list,
|
||||||
};
|
};
|
||||||
|
|
||||||
Some(model_field)
|
Some(model_field)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@ -156,13 +160,12 @@ pub fn get_actix_admin_fields_select_list(fields: &Vec<ModelField>) -> Vec<Token
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn get_field_for_primary_key(fields: &Vec<ModelField>) -> TokenStream {
|
pub fn get_field_for_primary_key(fields: &Vec<ModelField>) -> TokenStream {
|
||||||
let primary_key_model_field = fields
|
let primary_key_model_field = fields
|
||||||
.iter()
|
.iter()
|
||||||
// TODO: filter id attr based on struct attr or sea_orm primary_key attr
|
// TODO: filter id attr based on struct attr or sea_orm primary_key attr
|
||||||
.find(|model_field| model_field.primary_key)
|
.find(|model_field| model_field.primary_key)
|
||||||
.expect("model must have a single primary key");
|
.expect("model must have a single primary key");
|
||||||
|
|
||||||
let ident = primary_key_model_field.ident.to_owned();
|
let ident = primary_key_model_field.ident.to_owned();
|
||||||
|
|
||||||
@ -173,60 +176,61 @@ pub fn get_field_for_primary_key(fields: &Vec<ModelField>) -> TokenStream {
|
|||||||
|
|
||||||
pub fn get_primary_key_field_name(fields: &Vec<ModelField>) -> String {
|
pub fn get_primary_key_field_name(fields: &Vec<ModelField>) -> String {
|
||||||
let primary_key_model_field = fields
|
let primary_key_model_field = fields
|
||||||
.iter()
|
.iter()
|
||||||
// TODO: filter id attr based on struct attr or sea_orm primary_key attr
|
// TODO: filter id attr based on struct attr or sea_orm primary_key attr
|
||||||
.find(|model_field| model_field.primary_key)
|
.find(|model_field| model_field.primary_key)
|
||||||
.expect("model must have a single primary key");
|
.expect("model must have a single primary key");
|
||||||
|
|
||||||
primary_key_model_field.ident.to_string()
|
primary_key_model_field.ident.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_fields_for_from_model(fields: &Vec<ModelField>) -> Vec<TokenStream> {
|
pub fn get_fields_for_from_model(fields: &Vec<ModelField>) -> Vec<TokenStream> {
|
||||||
fields
|
fields
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|model_field| !model_field.primary_key)
|
.filter(|model_field| !model_field.primary_key)
|
||||||
.map(|model_field| {
|
.map(|model_field| {
|
||||||
let ident_name = model_field.ident.to_string();
|
let ident_name = model_field.ident.to_string();
|
||||||
let ident = model_field.ident.to_owned();
|
let ident = model_field.ident.to_owned();
|
||||||
|
|
||||||
match model_field.is_option() {
|
match model_field.is_option() {
|
||||||
true => {
|
true => {
|
||||||
quote! {
|
quote! {
|
||||||
#ident_name => match model.#ident {
|
#ident_name => match model.#ident {
|
||||||
Some(val) => val.to_string(),
|
Some(val) => val.to_string(),
|
||||||
None => "".to_owned()
|
None => "".to_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false => {
|
||||||
|
quote! {
|
||||||
|
#ident_name => model.#ident.to_string()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
false => {
|
.collect::<Vec<_>>()
|
||||||
quote! {
|
|
||||||
#ident_name => model.#ident.to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_fields_for_validate_model(fields: &Vec<ModelField>) -> Vec<TokenStream> {
|
pub fn get_fields_for_validate_model(fields: &Vec<ModelField>) -> Vec<TokenStream> {
|
||||||
fields
|
fields
|
||||||
.iter()
|
.iter()
|
||||||
// TODO: filter id attr based on struct attr or sea_orm primary_key attr
|
|
||||||
.filter(|model_field| !model_field.primary_key)
|
.filter(|model_field| !model_field.primary_key)
|
||||||
.map(|model_field| {
|
.map(|model_field| {
|
||||||
let ident_name = model_field.ident.to_string();
|
let ident_name = model_field.ident.to_string();
|
||||||
let ty = model_field.ty.to_owned();
|
let ty = model_field.ty.to_owned();
|
||||||
|
|
||||||
|
let is_option_or_string = model_field.is_option() || model_field.is_string();
|
||||||
|
|
||||||
match model_field.is_option() {
|
match model_field.is_option() {
|
||||||
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.insert(#ident_name.to_string(), err)).ok();
|
model.get_value::<#inner_ty>(#ident_name, #is_option_or_string).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.insert(#ident_name.to_string(), err)).ok();
|
model.get_value::<#ty>(#ident_name, #is_option_or_string).map_err(|err| errors.insert(#ident_name.to_string(), err)).ok()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -236,54 +240,72 @@ pub fn get_fields_for_validate_model(fields: &Vec<ModelField>) -> Vec<TokenStrea
|
|||||||
|
|
||||||
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()
|
||||||
// TODO: filter id attr based on struct attr or sea_orm primary_key attr
|
// TODO: filter id attr based on struct attr or sea_orm primary_key attr
|
||||||
.filter(|model_field| !model_field.primary_key)
|
.filter(|model_field| !model_field.primary_key)
|
||||||
.map(|model_field| {
|
.map(|model_field| {
|
||||||
let ident_name = model_field.ident.to_string();
|
let ident_name = model_field.ident.to_string();
|
||||||
let ident = model_field.ident.to_owned();
|
let ident = model_field.ident.to_owned();
|
||||||
let ty = model_field.ty.to_owned();
|
let ty = model_field.ty.to_owned();
|
||||||
|
|
||||||
match model_field.is_option() {
|
let is_option_or_string = model_field.is_option() || model_field.is_string();
|
||||||
true => {
|
|
||||||
let inner_ty = model_field.inner_type.to_owned().unwrap();
|
match model_field.is_option() {
|
||||||
quote! {
|
true => {
|
||||||
#ident: Set(model.get_value::<#inner_ty>(#ident_name).unwrap())
|
let inner_ty = model_field.inner_type.to_owned().unwrap();
|
||||||
|
quote! {
|
||||||
|
#ident: Set(model.get_value::<#inner_ty>(#ident_name, #is_option_or_string).unwrap())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
// TODO: this fails for empty string as it returns None which then cannot be unwrapped
|
||||||
false => {
|
// Should do maybe typecheck and if it is string return "" instead of None
|
||||||
quote! {
|
false => {
|
||||||
#ident: Set(model.get_value::<#ty>(#ident_name).unwrap().unwrap())
|
if model_field.is_string() {
|
||||||
|
quote! {
|
||||||
|
#ident: Set(model.get_value::<#ty>(#ident_name, #is_option_or_string).unwrap().unwrap_or(String::new()))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
#ident: Set(model.get_value::<#ty>(#ident_name, #is_option_or_string).unwrap().unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
})
|
.collect::<Vec<_>>()
|
||||||
.collect::<Vec<_>>()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_fields_for_edit_model(fields: &Vec<ModelField>) -> Vec<TokenStream> {
|
pub fn get_fields_for_edit_model(fields: &Vec<ModelField>) -> Vec<TokenStream> {
|
||||||
fields
|
fields
|
||||||
.iter()
|
.iter()
|
||||||
// TODO: filter id attr based on struct attr or sea_orm primary_key attr
|
// TODO: filter id attr based on struct attr or sea_orm primary_key attr
|
||||||
.filter(|model_field| !model_field.primary_key)
|
.filter(|model_field| !model_field.primary_key)
|
||||||
.map(|model_field| {
|
.map(|model_field| {
|
||||||
let ident_name = model_field.ident.to_string();
|
let ident_name = model_field.ident.to_string();
|
||||||
let ident = model_field.ident.to_owned();
|
let ident = model_field.ident.to_owned();
|
||||||
let ty = model_field.ty.to_owned();
|
let ty = model_field.ty.to_owned();
|
||||||
|
let is_option_or_string = model_field.is_option() || model_field.is_string();
|
||||||
|
|
||||||
match model_field.is_option() {
|
match model_field.is_option() {
|
||||||
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).unwrap())
|
entity.#ident = Set(model.get_value::<#inner_ty>(#ident_name, #is_option_or_string).unwrap())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
false => {
|
||||||
false => {
|
if model_field.is_string() {
|
||||||
quote! {
|
quote! {
|
||||||
entity.#ident = Set(model.get_value::<#ty>(#ident_name).unwrap().unwrap())
|
entity.#ident = Set(model.get_value::<#ty>(#ident_name, #is_option_or_string).unwrap().unwrap_or(String::new()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
quote! {
|
||||||
|
entity.#ident = Set(model.get_value::<#ty>(#ident_name, #is_option_or_string).unwrap().unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
})
|
.collect::<Vec<_>>()
|
||||||
.collect::<Vec<_>>()
|
|
||||||
}
|
}
|
@ -2,7 +2,6 @@ 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;
|
||||||
|
@ -49,14 +49,12 @@ impl From<String> for ActixAdminModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ActixAdminModel {
|
impl ActixAdminModel {
|
||||||
pub fn get_value<T: std::str::FromStr>(&self, key: &str) -> Result<Option<T>, String> {
|
pub fn get_value<T: std::str::FromStr>(&self, key: &str, is_option_or_string: bool) -> Result<Option<T>, String> {
|
||||||
let value = self.values.get(key);
|
let value = self.values.get(key);
|
||||||
println!("{:?}", key);
|
|
||||||
println!("{:?}", value);
|
|
||||||
|
|
||||||
let res: Result<Option<T>, String> = match value {
|
let res: Result<Option<T>, String> = match value {
|
||||||
Some(val) => {
|
Some(val) => {
|
||||||
if val.is_empty() {
|
if val.is_empty() && is_option_or_string {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +65,12 @@ impl ActixAdminModel {
|
|||||||
Err(_) => Err("Invalid Value".to_string()),
|
Err(_) => Err("Invalid Value".to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => Ok(None),
|
_ => {
|
||||||
|
match is_option_or_string {
|
||||||
|
true => Ok(None),
|
||||||
|
false => Err("Invalid Value".to_string()) // a missing value in the form for a non-optional value
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
res
|
res
|
||||||
|
@ -28,7 +28,7 @@ pub async fn create_get<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
|||||||
ctx.insert("model", &model);
|
ctx.insert("model", &model);
|
||||||
|
|
||||||
let body = TERA
|
let body = TERA
|
||||||
.render("edit.html", &ctx)
|
.render("create_or_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))
|
||||||
}
|
}
|
@ -25,10 +25,9 @@ pub async fn create_post<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>
|
|||||||
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);
|
ctx.insert("model", &model);
|
||||||
println!("{:?}", model.errors);
|
|
||||||
|
|
||||||
let body = TERA
|
let body = TERA
|
||||||
.render("edit.html", &ctx)
|
.render("create_or_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))
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ pub async fn edit_get<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
|||||||
ctx.insert("list_link", &E::get_list_link(&entity_name));
|
ctx.insert("list_link", &E::get_list_link(&entity_name));
|
||||||
|
|
||||||
let body = TERA
|
let body = TERA
|
||||||
.render("edit.html", &ctx)
|
.render("create_or_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))
|
||||||
}
|
}
|
@ -29,7 +29,7 @@ pub async fn edit_post<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
|||||||
ctx.insert("list_link", &E::get_list_link(&entity_name));
|
ctx.insert("list_link", &E::get_list_link(&entity_name));
|
||||||
|
|
||||||
let body = TERA
|
let body = TERA
|
||||||
.render("edit.html", &ctx)
|
.render("create_or_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,31 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<article>
|
|
||||||
<form method="post">
|
|
||||||
{% for model_field in view_model.fields -%}
|
|
||||||
<label for="{{ model_field.field_name }}">
|
|
||||||
{{ model_field.field_name }}
|
|
||||||
{% if model_field.select_list != "" %}
|
|
||||||
<select name="{{ model_field.field_name }}">
|
|
||||||
{% if model_field.is_option %}
|
|
||||||
<option value="" selected></option>
|
|
||||||
{% else %}
|
|
||||||
<option value="" selected disabled>Select</option>
|
|
||||||
{% endif %}
|
|
||||||
{% for select_list_item in select_lists[model_field.field_name] -%}
|
|
||||||
<option value="{{ select_list_item[0] }}">{{ select_list_item[1] }}</option>
|
|
||||||
{%- endfor %}
|
|
||||||
</select>
|
|
||||||
{% else %}
|
|
||||||
<input type="{{ model_field.html_input_type }}" name="{{ model_field.field_name }}"
|
|
||||||
placeholder="{{ model_field.field_name }}" aria-label="{{ model_field.field_name }}">
|
|
||||||
<!-- required="" -->
|
|
||||||
{% endif %}
|
|
||||||
</label>
|
|
||||||
{%- endfor %}
|
|
||||||
<button type="submit">Save</button>
|
|
||||||
<a href="{{ list_link }}" role="button" class="secondary">Cancel</a>
|
|
||||||
</form>
|
|
||||||
</article>
|
|
||||||
{% endblock content %}
|
|
@ -2,11 +2,6 @@
|
|||||||
|
|
||||||
{% 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 -%}
|
BIN
database.db-wal
BIN
database.db-wal
Binary file not shown.
Loading…
Reference in New Issue
Block a user