add struct attr and parsing for option

This commit is contained in:
manuel 2022-06-06 12:55:25 +02:00
parent 9b5bc5c4da
commit 244d1f9e01
12 changed files with 200 additions and 28 deletions

View File

@ -6,7 +6,7 @@ edition = "2021"
[dependencies] [dependencies]
actix-web = "4.0.1" actix-web = "4.0.1"
actix-rt = "2.7.0" actix-rt = "2.7.0"
actix-session = "0.5.0" actix-session = "0.6.2"
tera = "1.15.0" tera = "1.15.0"
actix_admin_macros = { path = "actix_admin_macros" } actix_admin_macros = { path = "actix_admin_macros" }
oauth2 = "4.1" oauth2 = "4.1"

View File

@ -9,12 +9,13 @@ proc-macro = true
[dependencies] [dependencies]
actix-web = "4.0.1" actix-web = "4.0.1"
actix-rt = "2.7.0" actix-rt = "2.7.0"
actix-session = "0.5.0" actix-session = "0.6.2"
tera = "1.15.0" tera = "1.15.0"
oauth2 = "4.1" oauth2 = "4.1"
base64 = "0.13.0" base64 = "0.13.0"
bae = "0.1.7"
quote = "1.0" quote = "1.0"
syn = { version = "1.0", features = ["full", "extra-traits"] } syn = { version = "1.0", features = ["full", "extra-traits"] }
proc-macro2 = { version = "1.0.36", default-features = false } proc-macro2 = { version = "1.0.36", default-features = false }

View File

@ -0,0 +1,28 @@
pub mod derive_attr {
use bae::FromAttributes;
#[derive(
Debug,
Eq,
PartialEq,
FromAttributes,
Default
)]
pub struct ActixAdmin {
pub inner_type: Option<syn::Type>,
// Anything that implements `syn::parse::Parse` is supported.
//mandatory_type: syn::Type,
//mandatory_ident: syn::Ident,
// Fields wrapped in `Option` are optional and default to `None` if
// not specified in the attribute.
//optional_missing: Option<syn::Type>,
//optional_given: Option<syn::Type>,
// A "switch" is something that doesn't take arguments.
// All fields with type `Option<()>` are considered swiches.
// They default to `None`.
//switch: Option<()>,
}
}

View File

@ -4,13 +4,15 @@ use quote::quote;
mod struct_fields; mod struct_fields;
use struct_fields::get_fields_for_tokenstream; use struct_fields::get_fields_for_tokenstream;
#[proc_macro_derive(DeriveActixAdminModel)] mod attributes;
#[proc_macro_derive(DeriveActixAdminModel, attributes(actix_admin))]
pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStream { pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let fields = get_fields_for_tokenstream(input); let fields = get_fields_for_tokenstream(input);
let names_const_fields_str = fields let names_const_fields_str = fields
.iter() .iter()
.map(|(_vis, ident, _ty)| { .map(|(_vis, ident, _ty, _is_option)| {
let ident_name = ident.to_string(); let ident_name = ident.to_string();
quote! { quote! {
#ident_name #ident_name
@ -21,11 +23,20 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
let fields_for_create_model = fields let fields_for_create_model = 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(|(_vis, ident, _ty)| !ident.to_string().eq("id")) .filter(|(_vis, ident, _ty, _is_option)| !ident.to_string().eq("id"))
.map(|(_vis, ident, ty)| { .map(|(_vis, ident, ty, is_option)| {
let ident_name = ident.to_string(); let ident_name = ident.to_string();
quote! { match is_option {
#ident: Set(model.get_value::<#ty>(#ident_name).unwrap()) true => {
quote! {
#ident: Set(model.get_value::<#ty>(#ident_name))
}
},
false => {
quote! {
#ident: Set(model.get_value::<#ty>(#ident_name).unwrap())
}
}
} }
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -33,22 +44,46 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
let fields_for_edit_model = fields let fields_for_edit_model = 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(|(_vis, ident, _ty)| !ident.to_string().eq("id")) .filter(|(_vis, ident, _ty, _is_option)| !ident.to_string().eq("id"))
.map(|(_vis, ident, ty)| { .map(|(_vis, ident, ty, is_option)| {
let ident_name = ident.to_string(); let ident_name = ident.to_string();
quote! { println!("edit {} {:?}", &ident_name, ty);
entity.#ident = Set(model.get_value::<#ty>(#ident_name).unwrap()) match is_option {
true => {
quote! {
entity.#ident = Set(model.get_value::<#ty>(#ident_name))
}
},
false => {
quote! {
entity.#ident = Set(model.get_value::<#ty>(#ident_name).unwrap())
}
}
} }
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let fields_for_from_model = fields let fields_for_from_model = fields
.iter() .iter()
.map(|(_vis, ident, _ty)| { .map(|(_vis, ident, _ty, is_option)| {
let ident_name = ident.to_string(); let ident_name = ident.to_string();
quote! { println!("from {} {:?}", &ident_name, _ty);
#ident_name => model.#ident.to_string()
match is_option {
true => {
quote! {
#ident_name => match model.#ident {
Some(val) => val.to_string(),
None => "".to_owned()
}
}
},
false => {
quote! {
#ident_name => model.#ident.to_string()
}
} }
}
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -104,6 +139,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 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;
println!("creating {:?}", model);
println!("operation {:?}", insert_operation);
model model
} }

View File

@ -3,9 +3,11 @@ use syn::{
Attribute, Fields, Meta, NestedMeta, Visibility, DeriveInput, Type Attribute, Fields, Meta, NestedMeta, Visibility, DeriveInput, Type
}; };
const ATTR_META_SKIP: &'static str = "skip"; use crate::attributes::derive_attr;
pub fn get_fields_for_tokenstream(input: proc_macro::TokenStream) -> std::vec::Vec<(syn::Visibility, proc_macro2::Ident, Type)> { const ACTIX_ADMIN: &'static str = "actix_admin";
pub fn get_fields_for_tokenstream(input: proc_macro::TokenStream) -> std::vec::Vec<(syn::Visibility, proc_macro2::Ident, Type, bool)> {
let ast: DeriveInput = syn::parse(input).unwrap(); let ast: DeriveInput = syn::parse(input).unwrap();
let (_vis, ty, _generics) = (&ast.vis, &ast.ident, &ast.generics); let (_vis, ty, _generics) = (&ast.vis, &ast.ident, &ast.generics);
let _names_struct_ident = Ident::new(&(ty.to_string() + "FieldStaticStr"), Span::call_site()); let _names_struct_ident = Ident::new(&(ty.to_string() + "FieldStaticStr"), Span::call_site());
@ -19,10 +21,16 @@ pub fn get_fields_for_tokenstream(input: proc_macro::TokenStream) -> std::vec::V
pub fn has_skip_attr(attr: &Attribute, path: &'static str) -> bool { pub fn has_skip_attr(attr: &Attribute, path: &'static str) -> bool {
if let Ok(Meta::List(meta_list)) = attr.parse_meta() { if let Ok(Meta::List(meta_list)) = attr.parse_meta() {
//println!("1");
//println!("{:?}", meta_list.path);
//println!("{}", path);
if meta_list.path.is_ident(path) { if meta_list.path.is_ident(path) {
//println!("2");
for nested_item in meta_list.nested.iter() { for nested_item in meta_list.nested.iter() {
if let NestedMeta::Meta(Meta::Path(path)) = nested_item { if let NestedMeta::Meta(Meta::Path(path)) = nested_item {
if path.is_ident(ATTR_META_SKIP) { //println!("3");
if path.is_ident(ACTIX_ADMIN) {
//println!("true");
return true; return true;
} }
} }
@ -32,24 +40,84 @@ pub fn has_skip_attr(attr: &Attribute, path: &'static str) -> bool {
false false
} }
pub fn filter_fields(fields: &Fields) -> Vec<(Visibility, Ident, Type)> { pub fn get_field_type<'a>(actix_admin_attr: &'a Option<derive_attr::ActixAdmin>, field: &'a syn::Field) -> &'a syn::Type {
match actix_admin_attr {
Some(attr) => {
match &attr.inner_type {
Some(inner_type) => &inner_type,
None => &field.ty
}
},
_ => &field.ty
}
}
pub fn filter_fields(fields: &Fields) -> Vec<(Visibility, Ident, Type, bool)> {
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();
if field if field
.attrs .attrs
.iter() .iter()
.find(|attr| has_skip_attr(attr, "struct_field_names")) .find(|attr| has_skip_attr(attr, ACTIX_ADMIN))
.is_none() .is_none()
&& field.ident.is_some() && 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 field_ty = field.ty.to_owned(); println!("{}", field_ident.to_string());
Some((field_vis, field_ident, field_ty)) let is_option = extract_type_from_option(&field.ty).is_some();
let field_ty = get_field_type(&actix_admin_attr, &field).to_owned();
Some((field_vis, field_ident, field_ty, is_option))
} else { } else {
None None
} }
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
}
fn extract_type_from_option(ty: &syn::Type) -> Option<&syn::Type> {
use syn::{GenericArgument, Path, PathArguments, PathSegment};
fn extract_type_path(ty: &syn::Type) -> Option<&Path> {
match *ty {
syn::Type::Path(ref typepath) if typepath.qself.is_none() => Some(&typepath.path),
_ => None,
}
}
// TODO store (with lazy static) the vec of string
// TODO maybe optimization, reverse the order of segments
fn extract_option_segment(path: &Path) -> Option<&PathSegment> {
let idents_of_path = path
.segments
.iter()
.into_iter()
.fold(String::new(), |mut acc, v| {
acc.push_str(&v.ident.to_string());
acc.push('|');
acc
});
vec!["Option|", "std|option|Option|", "core|option|Option|"]
.into_iter()
.find(|s| &idents_of_path == *s)
.and_then(|_| path.segments.last())
}
extract_type_path(ty)
.and_then(|path| extract_option_segment(path))
.and_then(|path_seg| {
let type_params = &path_seg.arguments;
// It should have only on angle-bracketed param ("<String>"):
match *type_params {
PathArguments::AngleBracketed(ref params) => params.args.first(),
_ => None,
}
})
.and_then(|generic_arg| match *generic_arg {
GenericArgument::Type(ref ty) => Some(ty),
_ => None,
})
} }

View File

@ -1,4 +1,3 @@
use actix_web::http::header;
use actix_web::{web, Error, HttpRequest, HttpResponse}; use actix_web::{web, Error, HttpRequest, HttpResponse};
use crate::prelude::*; use crate::prelude::*;
@ -6,12 +5,12 @@ use crate::prelude::*;
pub async fn delete_post<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>( pub async fn delete_post<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
_req: HttpRequest, _req: HttpRequest,
data: web::Data<T>, data: web::Data<T>,
text: String, _text: String,
id: web::Path<i32> id: web::Path<i32>
) -> 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 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();
// TODO:handle any errors // TODO:handle any errors

View File

@ -9,7 +9,7 @@ pub async fn edit_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,
id: web::Path<i32> id: web::Path<i32>
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, Error> {
let db = &data.get_db(); let db = &data.get_db();

View File

@ -37,6 +37,7 @@ pub async fn list<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
let result: (usize, Vec<ActixAdminModel>) = E::list(db, page, entities_per_page).await; let result: (usize, Vec<ActixAdminModel>) = E::list(db, page, entities_per_page).await;
let entities = result.1; let entities = result.1;
let num_pages = result.0; let num_pages = result.0;
println!("{:?}", entities);
let mut ctx = Context::new(); let mut ctx = Context::new();
ctx.insert("entity_names", &entity_names); ctx.insert("entity_names", &entity_names);

Binary file not shown.

Binary file not shown.

View File

@ -25,6 +25,8 @@ pub async fn create_post_table(db: &DbConn) -> Result<ExecResult, DbErr> {
) )
.col(ColumnDef::new(post::Column::Title).string().not_null()) .col(ColumnDef::new(post::Column::Title).string().not_null())
.col(ColumnDef::new(post::Column::Text).string().not_null()) .col(ColumnDef::new(post::Column::Text).string().not_null())
.col(ColumnDef::new(post::Column::TeaMandatory).string().not_null())
.col(ColumnDef::new(post::Column::TeaOptional).string())
.to_owned(); .to_owned();
create_table(db, &stmt).await; create_table(db, &stmt).await;

View File

@ -1,6 +1,9 @@
use sea_orm::entity::prelude::*; use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use actix_admin::prelude::*; use actix_admin::prelude::*;
use std::str::FromStr;
use std::fmt;
use std::fmt::Display;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize, DeriveActixAdminModel)] #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize, DeriveActixAdminModel)]
#[sea_orm(table_name = "post")] #[sea_orm(table_name = "post")]
@ -11,9 +14,42 @@ pub struct Model {
pub title: String, pub title: String,
#[sea_orm(column_type = "Text")] #[sea_orm(column_type = "Text")]
pub text: String, pub text: String,
pub tea_mandatory: Tea,
#[actix_admin(inner_type=Tea)]
pub tea_optional: Option<Tea>,
} }
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {} pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {} impl ActiveModelBehavior for ActiveModel {}
#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum, Deserialize, Serialize)]
#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "tea")]
pub enum Tea {
#[sea_orm(string_value = "EverydayTea")]
EverydayTea,
#[sea_orm(string_value = "BreakfastTea")]
BreakfastTea,
}
impl FromStr for Tea {
type Err = ();
fn from_str(input: &str) -> Result<Tea, Self::Err> {
match input {
"EverydayTea" => Ok(Tea::EverydayTea),
"BreakfastTea" => Ok(Tea::BreakfastTea),
_ => Err(()),
}
}
}
impl Display for Tea {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
match &*self {
Tea::EverydayTea => write!(formatter, "{}", String::from("EverydayTea")),
Tea::BreakfastTea => write!(formatter, "{}", String::from("BreakfastTea")),
}
}
}