implement macros for active_model generation

This commit is contained in:
Manuel Gugger 2022-05-25 13:11:54 +02:00
parent 88a754eeac
commit 7c8cf72668
10 changed files with 105 additions and 56 deletions

View File

@ -23,7 +23,7 @@ serde = "1.0.136"
serde_json = "1.0.79"
serde_derive = "1.0.136"
quote = "1.0"
sea-orm = { version = "0.6.0", features = [ "sqlx-sqlite", "runtime-actix-native-tls", "macros" ], default-features = false }
sea-orm = { version = "0.8.0", features = [ "sqlx-sqlite", "runtime-actix-native-tls", "macros" ], default-features = false }
syn = "1.0.91"
actix_admin = { path = "actix_admin" }
azure_auth = { path = "azure_auth" }

View File

@ -21,4 +21,4 @@ futures = "0.3.21"
serde = "1.0.136"
serde_json = "1.0.79"
serde_derive = "1.0.136"
sea-orm = { version = "0.6.0", features = [ "sqlx-sqlite", "runtime-actix-native-tls", "macros" ], default-features = false }
sea-orm = { version = "0.8.0", features = [ "sqlx-sqlite", "runtime-actix-native-tls", "macros" ], default-features = false }

View File

@ -27,4 +27,4 @@ futures = "0.3.21"
serde = "1.0.136"
serde_json = "1.0.79"
serde_derive = "1.0.136"
sea-orm = { version = "0.6.0", features = [ "sqlx-sqlite", "runtime-actix-native-tls", "macros" ], default-features = false }
sea-orm = { version = "0.8.0", features = [ "sqlx-sqlite", "runtime-actix-native-tls", "macros" ], default-features = false }

View File

@ -8,12 +8,37 @@ use struct_fields::get_fields_for_tokenstream;
pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let fields = get_fields_for_tokenstream(input);
let names_const_fields_str = fields.iter().map(|(_vis, ident)| {
let ident_name = ident.to_string();
quote! {
#ident_name
}
});
let names_const_fields_str = fields
.iter()
.map(|(_vis, ident, _ty)| {
let ident_name = ident.to_string();
quote! {
#ident_name
}
})
.collect::<Vec<_>>();
let fields_for_create_model = fields
.iter()
// TODO: filter id attr based on struct attr or sea_orm primary_key attr
.filter(|(_vis, ident, _ty)| !ident.to_string().eq("id"))
.map(|(_vis, ident, ty)| {
let ident_name = ident.to_string();
quote! {
#ident: Set(model.get_value::<#ty>(#ident_name).unwrap())
}
})
.collect::<Vec<_>>();
let fields_for_from_model = fields
.iter()
.map(|(_vis, ident, _ty)| {
let ident_name = ident.to_string();
quote! {
#ident_name => model.#ident.to_string()
}
})
.collect::<Vec<_>>();
let expanded = quote! {
use std::convert::From;
@ -25,6 +50,7 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
use sea_orm::{entity::*, query::*};
use std::collections::HashMap;
use sea_orm::EntityTrait;
use quote::quote;
impl From<Entity> for ActixAdminViewModel {
fn from(entity: Entity) -> Self {
@ -40,9 +66,10 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
ActixAdminModel {
// TODO: create dynamically
values: hashmap![
"title" => model.title,
"text" => model.text,
"id" => model.id.to_string()
#(#fields_for_from_model),*
// "title" => model.title,
// "text" => model.text,
// "id" => model.id.to_string()
]
}
}
@ -50,9 +77,10 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
impl From<ActixAdminModel> for ActiveModel {
fn from(model: ActixAdminModel) -> Self {
ActiveModel {
title: Set(model.values.get("title").unwrap().to_string()),
text: Set(model.values.get("text").unwrap().to_string()),
ActiveModel
{
#(#fields_for_create_model),*
,
..Default::default()
}
}
@ -71,6 +99,10 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
model
}
fn get_entity_name() -> String {
Entity.table_name().to_string()
}
}
#[async_trait]

View File

@ -1,11 +1,11 @@
use proc_macro2::{Span, Ident};
use syn::{
Attribute, Fields, Meta, NestedMeta, Visibility, DeriveInput
Attribute, Fields, Meta, NestedMeta, Visibility, DeriveInput, Type
};
const ATTR_META_SKIP: &'static str = "skip";
pub fn get_fields_for_tokenstream(input: proc_macro::TokenStream) -> std::vec::Vec<(syn::Visibility, proc_macro2::Ident)> {
pub fn get_fields_for_tokenstream(input: proc_macro::TokenStream) -> std::vec::Vec<(syn::Visibility, proc_macro2::Ident, Type)> {
let ast: DeriveInput = syn::parse(input).unwrap();
let (_vis, ty, _generics) = (&ast.vis, &ast.ident, &ast.generics);
let _names_struct_ident = Ident::new(&(ty.to_string() + "FieldStaticStr"), Span::call_site());
@ -32,7 +32,7 @@ pub fn has_skip_attr(attr: &Attribute, path: &'static str) -> bool {
false
}
pub fn filter_fields(fields: &Fields) -> Vec<(Visibility, Ident)> {
pub fn filter_fields(fields: &Fields) -> Vec<(Visibility, Ident, Type)> {
fields
.iter()
.filter_map(|field| {
@ -45,7 +45,8 @@ pub fn filter_fields(fields: &Fields) -> Vec<(Visibility, Ident)> {
{
let field_vis = field.vis.clone();
let field_ident = field.ident.as_ref().unwrap().clone();
Some((field_vis, field_ident))
let field_ty = field.ty.to_owned();
Some((field_vis, field_ident, field_ty))
} else {
None
}

View File

@ -20,14 +20,6 @@ macro_rules! hashmap {
}}
}
#[macro_export]
macro_rules! make_fields {
($($element: ident: $ty: ty),*) => {
$($element: $ty),*
}
}
// globals
lazy_static! {
static ref TERA: Tera =
@ -70,6 +62,7 @@ pub struct ActixAdminModel {
pub trait ActixAdminViewModelTrait {
async fn list(db: &DatabaseConnection, page: usize, entities_per_page: usize) -> Vec<ActixAdminModel>;
async fn create_entity(db: &DatabaseConnection, model: ActixAdminModel) -> ActixAdminModel;
fn get_entity_name() -> String;
}
#[derive(Clone, Debug, Serialize)]
@ -94,8 +87,8 @@ impl ActixAdmin {
actix_admin
}
pub fn add_entity(mut self, view_model: &ActixAdminViewModel) -> Self {
self.entity_names.push(view_model.entity_name.to_string());
pub fn add_entity<E: ActixAdminViewModelTrait>(mut self, view_model: &ActixAdminViewModel) -> Self {
self.entity_names.push(E::get_entity_name());
let view_model_cloned = view_model.clone();
let key = view_model.entity_name.to_string();
self.view_models.insert(key, view_model_cloned);
@ -124,6 +117,17 @@ impl From<String> for ActixAdminModel {
}
}
impl ActixAdminModel {
pub fn get_value<T: std::str::FromStr>(&self, key: &str) -> Option<T> {
println!("get value for key {}", key);
let value = self.values.get(key).unwrap().to_string().parse::<T>();
match value {
Ok(val) => Some(val),
Err(_) => None //panic!("key {} could not be parsed", key)
}
}
}
pub async fn index<T: AppDataTrait>(data: web::Data<T>) -> Result<HttpResponse, Error> {
let entity_names = &data.get_actix_admin().entity_names;
let mut ctx = Context::new();
@ -135,8 +139,8 @@ pub async fn index<T: AppDataTrait>(data: web::Data<T>) -> Result<HttpResponse,
Ok(HttpResponse::Ok().content_type("text/html").body(body))
}
pub async fn list<T: AppDataTrait, E: ActixAdminViewModelTrait>(req: HttpRequest, data: web::Data<T>, path: web::Path<String>) -> Result<HttpResponse, Error> {
let entity_name: String = path.into_inner();
pub async fn list<T: AppDataTrait, E: ActixAdminViewModelTrait>(req: HttpRequest, data: web::Data<T>) -> Result<HttpResponse, Error> {
let entity_name = E::get_entity_name();
let actix_admin = data.get_actix_admin();
let view_model: &ActixAdminViewModel = actix_admin.view_models.get(&entity_name).unwrap();
let entity_names = &data.get_actix_admin().entity_names;
@ -163,10 +167,9 @@ pub async fn list<T: AppDataTrait, E: ActixAdminViewModelTrait>(req: HttpRequest
Ok(HttpResponse::Ok().content_type("text/html").body(body))
}
pub async fn create_get<T: AppDataTrait, E: ActixAdminViewModelTrait>(_req: HttpRequest, data: web::Data<T>, _body: web::Payload, _text: String, entity_name: web::Path<String>) -> Result<HttpResponse, Error> {
pub async fn create_get<T: AppDataTrait, E: ActixAdminViewModelTrait>(_req: HttpRequest, data: web::Data<T>, _body: web::Payload, _text: String) -> Result<HttpResponse, Error> {
let _db = &data.get_db();
let entity_name: String = entity_name.into_inner();
println!("{}", &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();
@ -184,9 +187,9 @@ pub async fn create_get<T: AppDataTrait, E: ActixAdminViewModelTrait>(_req: Http
Ok(HttpResponse::Ok().content_type("text/html").body(body))
}
pub async fn create_post<T: AppDataTrait, E: ActixAdminViewModelTrait>(_req: HttpRequest, data: web::Data<T>, text: String, entity_name: web::Path<String>) -> Result<HttpResponse, Error> {
pub async fn create_post<T: AppDataTrait, E: ActixAdminViewModelTrait>(_req: HttpRequest, data: web::Data<T>, text: String) -> Result<HttpResponse, Error> {
let db = &data.get_db();
let entity_name: String = entity_name.into_inner();
let entity_name = E::get_entity_name();
let actix_admin = data.get_actix_admin();
let view_model = actix_admin.view_models.get(&entity_name).unwrap();

Binary file not shown.

View File

@ -1,9 +1,9 @@
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
//use actix_admin::{ DeriveActixAdminModel };
use actix_admin::{ DeriveActixAdminModel };
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize/*, DeriveActixAdminModel*/)]
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize, DeriveActixAdminModel)]
#[sea_orm(table_name = "comment")]
pub struct Model {
#[sea_orm(primary_key)]

View File

@ -1,10 +1,10 @@
// setup
use sea_orm::sea_query::{ColumnDef, TableCreateStatement};
use sea_orm::{error::*, sea_query, ConnectionTrait, DbConn, ExecResult};
pub mod post;
pub mod comment;
pub use post::Entity as Post;
pub mod post;
pub use comment::Entity as Comment;
pub use post::Entity as Post;
// setup
async fn create_table(db: &DbConn, stmt: &TableCreateStatement) -> Result<ExecResult, DbErr> {
@ -23,16 +23,24 @@ pub async fn create_post_table(db: &DbConn) -> Result<ExecResult, DbErr> {
.auto_increment()
.primary_key(),
)
.col(ColumnDef::new(post::Column::Title).string().not_null())
.col(ColumnDef::new(post::Column::Text).string().not_null())
.to_owned();
create_table(db, &stmt).await;
let stmt = sea_query::Table::create()
.table(comment::Entity)
.if_not_exists()
.col(
ColumnDef::new(post::Column::Title)
.string()
.not_null(),
)
.col(
ColumnDef::new(post::Column::Text)
.string()
.not_null(),
ColumnDef::new(post::Column::Id)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(ColumnDef::new(comment::Column::Comment).string().not_null())
.col(ColumnDef::new(comment::Column::User).string().not_null())
.to_owned();
create_table(db, &stmt).await

View File

@ -3,7 +3,6 @@ extern crate serde_derive;
use actix_admin::{
ActixAdmin, ActixAdminViewModel,
AppDataTrait as ActixAdminAppDataTrait,
ActixAdminViewModelTrait
};
use actix_session::{CookieSession, Session};
use actix_web::{web, App, HttpResponse, HttpServer, middleware};
@ -17,7 +16,7 @@ use std::time::Duration;
use tera::{Context, Tera};
mod entity;
use entity::{Post};
use entity::{Post, Comment};
#[derive(Debug, Clone)]
pub struct AppState {
@ -63,11 +62,17 @@ fn setup_actix_admin(
actix_admin
.create_scope::<AppState>()
.service(
web::scope("/{entity_name}")
web::scope("/post")
.route("/list", web::get().to(actix_admin::list::<AppState, Post>))
.route("/create", web::get().to(actix_admin::create_get::<AppState, Post>))
.route("/create", web::post().to(actix_admin::create_post::<AppState, Post>))
)
.service(
web::scope("/comment")
.route("/list", web::get().to(actix_admin::list::<AppState, Comment>))
.route("/create", web::get().to(actix_admin::create_get::<AppState, Comment>))
.route("/create", web::post().to(actix_admin::create_post::<AppState, Comment>))
)
}
#[actix_rt::main]
@ -105,10 +110,10 @@ async fn main() {
let _ = entity::create_post_table(&conn).await;
let post_view_model = ActixAdminViewModel::from(Post);
//let comment_view_model = ActixAdminViewModel::from(Comment);
let comment_view_model = ActixAdminViewModel::from(Comment);
let actix_admin = ActixAdmin::new()
.add_entity(&post_view_model)
//.add_entity::<AppState>(&comment_view_model)
.add_entity::<Post>(&post_view_model)
.add_entity::<Comment>(&comment_view_model)
;
let app_state = AppState {
oauth: client,