diff --git a/derive/src/lib.rs b/derive/src/lib.rs index a4195fac..a16f5931 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -6,6 +6,7 @@ mod error; mod filter; mod relation; mod root_query; +mod mutation; #[proc_macro_derive(Filter, attributes(sea_orm))] pub fn derive_filter_fn(input: proc_macro::TokenStream) -> proc_macro::TokenStream { @@ -135,6 +136,43 @@ pub fn derive_root_query_fn(input: proc_macro::TokenStream) -> proc_macro::Token res.into() } +#[proc_macro_derive(Mutation, attributes(sea_orm, seaography_mutation))] +pub fn derive_mutation_fn(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let DeriveInput { + ident, data, attrs, .. + } = syn::parse_macro_input!(input as syn::DeriveInput); + + let item = match data { + syn::Data::Struct(item) => item, + _ => { + return quote::quote! { + compile_error!("Input not structure") + } + .into() + } + }; + + if ident.ne("Model") { + return quote::quote! { + compile_error!("Struct must be SeaOrm Model structure") + } + .into(); + } + + let attrs = mutation::SeaOrm::from_attributes(&attrs).unwrap(); + + mutation::mutation_fn(item, attrs) + .unwrap_or_else(|err| { + let error = format!("{:?}", err); + + quote::quote! { + compile_error!(#error) + } + }) + .into() +} + + #[proc_macro_derive(EnumFilter, attributes())] pub fn derive_enum_filter_fn(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let DeriveInput { ident, data, .. } = syn::parse_macro_input!(input as syn::DeriveInput); diff --git a/derive/src/mutation.rs b/derive/src/mutation.rs new file mode 100644 index 00000000..f9ce7df7 --- /dev/null +++ b/derive/src/mutation.rs @@ -0,0 +1,218 @@ +use proc_macro2::{Ident, TokenStream, Span}; +use quote::quote; + +#[derive(Debug, Eq, PartialEq, bae::FromAttributes, Clone)] +pub struct Seaography { + entity: Option, + object_config: Option, + not_mutable: Option, +} + +#[derive(Debug, Eq, PartialEq, bae::FromAttributes)] +pub struct SeaographyMutation { + table_name: Option, + mutation_root: Option, + skip: Option +} + +// copy of filter::SeaOrm to make table_name public +#[derive(Debug, Eq, PartialEq, bae::FromAttributes)] +pub struct SeaOrm { + table_name: Option, +} + +// bool is meant as "skip", so this entry cant be created/updated/deleted +pub type IdentTypeTuple = (syn::Ident, syn::Type, bool); + +pub fn mutation_fn(item: syn::DataStruct, attrs_seaorm: SeaOrm) -> Result { + + let fields: Vec = item + .fields + .into_iter() + .map(|field| { + ( + field.ident.unwrap(), + field.ty, + field.attrs + .into_iter() + .any(|attr| { + if let Ok(c) = SeaographyMutation::from_attributes(&[attr]) { + c.skip.is_some() + } else { + false + } + }) + ) + }) + .collect(); + + let name = if let syn::Lit::Str(item) = attrs_seaorm.table_name.as_ref().unwrap() { + Ok(item.value().parse::()?) + } else { + Err(crate::error::Error::Internal( + "Unreachable parse of query entities".into(), + )) + }?; + + let create_mutation_query = create_mutation(&name, &fields); + let delete_mutation_query = delete_mutation(&name); + let update_mutation_query = update_mutation(&name, &fields); + + let struct_name = Ident::new(&format!("{}Mutation", &name), Span::call_site()); + + Ok(quote! { + + #[derive(Default)] + pub struct #struct_name; + + #[async_graphql::Object] + impl #struct_name { + #create_mutation_query + #delete_mutation_query + #update_mutation_query + } + }) + +} + +pub fn create_mutation(name: &TokenStream, fields: &[IdentTypeTuple]) -> TokenStream { + + let variables: Vec = fields + .iter() + .filter(|(_, _, skip)| { !*skip }) + .map(|(i, tp, _)| { + quote! { + #i: #tp, + } + }) + .collect(); + + + let variables_set: Vec = fields + .iter() + .filter(|(_, _, i)| { !*i }) + .map(|(i, _, _)| { + quote! { + #i: Set(#i), + } + }) + .collect(); + + let fn_name = Ident::new(&format!("create_{}", *name), Span::call_site()); + + quote! { + + pub async fn #fn_name<'a>( + &self, + ctx: &async_graphql::Context<'a>, + #(#variables)* + ) -> async_graphql::Result { + + use async_graphql::*; + use sea_orm::prelude::*; + use sea_orm::{NotSet, Set, IntoActiveModel, ActiveModelTrait, ModelTrait, EntityTrait, QueryFilter, ColumnTrait}; + + let db: &crate::DatabaseConnection = ctx.data::().unwrap(); + + let c = super::#name::ActiveModel { + #(#variables_set)* + ..Default::default() + }; + + let res = c.insert(db).await?; + + Ok(true) + } + } +} + +pub fn delete_mutation(name: &TokenStream) -> TokenStream { + + let fn_name = Ident::new(&format!("delete_{}", *name), Span::call_site()); + + quote! { + + pub async fn #fn_name<'a>( + &self, + ctx: &async_graphql::Context<'a>, + filters: super::#name::Filter, + ) -> async_graphql::Result { + use async_graphql::*; + use sea_orm::prelude::*; + use sea_orm::{EntityTrait}; + use seaography::{EntityOrderBy, EntityFilter}; + + let db: &crate::DatabaseConnection = ctx.data::().unwrap(); + let res = super::#name::Entity::delete_many() + .filter(filters.filter_condition()) + .exec(db) + .await?; + + Ok(true) + } + } +} + + + + +pub fn update_mutation(name: &TokenStream, fields: &[IdentTypeTuple]) -> TokenStream { + + let variables: Vec = fields + .iter() + .filter(|(_, _, skip)| { !*skip }) + .map(|(i, tp, _)| { + quote! { + #i: Option<#tp>, + } + }) + .collect(); + + + let variables_set: Vec = fields + .iter() + .filter(|(_, _, i)| { !*i }) + .map(|(i, _, _)| { + quote! { + + //c.#i = Set(v) + + if let Some(v) = #i { + c.#i = Set(v); + } + } + }) + .collect(); + + let fn_name = Ident::new(&format!("update_{}", *name), Span::call_site()); + + quote! { + + pub async fn #fn_name<'a>( + &self, + ctx: &async_graphql::Context<'a>, + filters: super::#name::Filter, + #(#variables)* + ) -> async_graphql::Result { + + use async_graphql::*; + use sea_orm::prelude::*; + use sea_orm::{NotSet, Set, IntoActiveModel, ActiveModelTrait, ModelTrait, EntityTrait, QueryFilter, ColumnTrait}; + use seaography::EntityFilter; + + let db: &crate::DatabaseConnection = ctx.data::().unwrap(); + + let mut c: super::#name::ActiveModel =Default::default(); + + #(#variables_set)* + + let res = super::#name::Entity::update_many() + .set(c) + .filter(filters.filter_condition()) + .exec(db) + .await?; + + Ok(true) + } + } +}