DieselCrud traits and derives
This commit is contained in:
20
crates/diesel_crud_derive/src/common.rs
Normal file
20
crates/diesel_crud_derive/src/common.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use quote::quote;
|
||||
|
||||
pub(crate) fn function_body(body: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
|
||||
quote! {
|
||||
let connection = self.pool.get().await;
|
||||
match connection {
|
||||
Ok(mut connection) => {
|
||||
use diesel::associations::HasTable;
|
||||
#body
|
||||
}
|
||||
Err(error) => Err(lib::diesel_crud_trait::CrudError::PoolError(error.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn return_type(output: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
|
||||
quote! {
|
||||
std::pin::Pin<Box<dyn core::future::Future<Output = Result<#output, lib::diesel_crud_trait::CrudError>> + Send + 'b>>
|
||||
}
|
||||
}
|
49
crates/diesel_crud_derive/src/create.rs
Normal file
49
crates/diesel_crud_derive/src/create.rs
Normal file
@ -0,0 +1,49 @@
|
||||
use crate::{common, StructAttributes};
|
||||
use proc_macro2::Ident;
|
||||
use quote::quote;
|
||||
use syn::Expr;
|
||||
|
||||
pub(crate) fn derive_diesel_crud_create_impl(
|
||||
StructAttributes {
|
||||
table,
|
||||
entity,
|
||||
create,
|
||||
..
|
||||
}: &StructAttributes,
|
||||
identifier: &Ident,
|
||||
) -> proc_macro2::TokenStream {
|
||||
let body = function_body(table);
|
||||
let return_type = common::return_type(quote! { Self::Entity });
|
||||
|
||||
quote! {
|
||||
#[automatically_derived]
|
||||
impl<'insertable, 'entity> lib::diesel_crud_trait::DieselCrudCreate<'insertable, 'entity, #table::table> for #identifier
|
||||
where
|
||||
'entity: 'insertable,
|
||||
{
|
||||
type Create = #create;
|
||||
type Entity = #entity;
|
||||
fn create<'a, 'b>(&'a self, create: &'insertable Self::Create) -> #return_type
|
||||
where
|
||||
Self: Sync + 'a,
|
||||
'a: 'b,
|
||||
'insertable: 'b
|
||||
{
|
||||
Box::pin(async move {
|
||||
#body
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn function_body(table: &Expr) -> proc_macro2::TokenStream {
|
||||
common::function_body(quote! {
|
||||
diesel_async::RunQueryDsl::get_result(
|
||||
diesel::dsl::insert_into(#table::table::table()).values(create),
|
||||
&mut connection
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
})
|
||||
}
|
55
crates/diesel_crud_derive/src/delete.rs
Normal file
55
crates/diesel_crud_derive/src/delete.rs
Normal file
@ -0,0 +1,55 @@
|
||||
use crate::{common, StructAttributes};
|
||||
use proc_macro2::Ident;
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::Expr;
|
||||
|
||||
pub(crate) fn derive_diesel_crud_delete_impl(
|
||||
StructAttributes {
|
||||
table,
|
||||
pk,
|
||||
pk_field,
|
||||
..
|
||||
}: &StructAttributes,
|
||||
identifier: &Ident,
|
||||
) -> proc_macro2::TokenStream {
|
||||
let body = function_body(
|
||||
table,
|
||||
pk_field
|
||||
.clone()
|
||||
.map(Expr::into_token_stream)
|
||||
.unwrap_or_else(|| quote! { id }),
|
||||
);
|
||||
let return_type = common::return_type(quote! { usize });
|
||||
|
||||
quote! {
|
||||
#[automatically_derived]
|
||||
impl lib::diesel_crud_trait::DieselCrudDelete for #identifier {
|
||||
type PK = #pk;
|
||||
fn delete<'a, 'pk, 'b>(&'a self, pk: &'pk Self::PK) -> #return_type
|
||||
where
|
||||
Self: Sync + 'a,
|
||||
'a: 'b,
|
||||
'pk: 'b,
|
||||
{
|
||||
Box::pin(async move {
|
||||
#body
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn function_body(table: &Expr, pk_field: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
|
||||
common::function_body(quote! {
|
||||
use diesel::QueryDsl;
|
||||
diesel_async::RunQueryDsl::execute(
|
||||
diesel::delete(
|
||||
#table::table
|
||||
.filter(diesel::expression_methods::ExpressionMethods::eq(#table::#pk_field, pk))
|
||||
),
|
||||
&mut connection,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
})
|
||||
}
|
183
crates/diesel_crud_derive/src/lib.rs
Normal file
183
crates/diesel_crud_derive/src/lib.rs
Normal file
@ -0,0 +1,183 @@
|
||||
extern crate proc_macro;
|
||||
|
||||
use crate::create::derive_diesel_crud_create_impl;
|
||||
use crate::delete::derive_diesel_crud_delete_impl;
|
||||
use crate::list::derive_diesel_crud_list_impl;
|
||||
use crate::read::derive_diesel_crud_read_impl;
|
||||
use crate::update::derive_diesel_crud_update_impl;
|
||||
use deluxe::{extract_attributes, ExtractAttributes};
|
||||
use quote::quote;
|
||||
use syn::{parse_macro_input, DeriveInput, Expr, Type};
|
||||
|
||||
mod common;
|
||||
mod create;
|
||||
mod delete;
|
||||
mod list;
|
||||
mod read;
|
||||
mod update;
|
||||
|
||||
#[derive(ExtractAttributes)]
|
||||
#[deluxe(attributes(diesel_crud))]
|
||||
pub(crate) struct StructAttributes {
|
||||
table: Expr,
|
||||
#[deluxe(default)]
|
||||
entity: Option<Type>,
|
||||
#[deluxe(default)]
|
||||
pk: Option<Type>,
|
||||
#[deluxe(default)]
|
||||
pk_field: Option<Expr>,
|
||||
#[deluxe(default)]
|
||||
create: Option<Type>, // TODO if None, use entity?
|
||||
#[deluxe(default)]
|
||||
update: Option<Type>, // TODO if None, use entity?
|
||||
}
|
||||
|
||||
// TODO get pool field automatically or by attribute
|
||||
|
||||
/// Derives 5 functions for CRUD operations
|
||||
/// 1. create
|
||||
/// 2. read
|
||||
/// 3. update
|
||||
/// 4. delete
|
||||
/// 5. list
|
||||
#[proc_macro_derive(DieselCrud, attributes(diesel_crud))]
|
||||
pub fn derive_diesel_crud(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let mut item = parse_macro_input!(item as DeriveInput);
|
||||
let struct_attributes = extract_attributes(&mut item).unwrap();
|
||||
let identifier = item.ident;
|
||||
|
||||
let create = derive_diesel_crud_create_impl(&struct_attributes, &identifier);
|
||||
let read = derive_diesel_crud_read_impl(&struct_attributes, &identifier);
|
||||
let update = derive_diesel_crud_update_impl(&struct_attributes, &identifier);
|
||||
let delete = derive_diesel_crud_delete_impl(&struct_attributes, &identifier);
|
||||
let list = derive_diesel_crud_list_impl(&struct_attributes, &identifier);
|
||||
|
||||
let table = struct_attributes.table;
|
||||
let expanded = quote! {
|
||||
#create
|
||||
#read
|
||||
#update
|
||||
#delete
|
||||
#list
|
||||
|
||||
impl<'insertable, 'entity> lib::diesel_crud_trait::DieselCrud<'insertable, 'entity, #table::table> for #identifier
|
||||
where
|
||||
'entity: 'insertable
|
||||
{}
|
||||
};
|
||||
expanded.into()
|
||||
}
|
||||
|
||||
/// Derives the create function for CRUD operations.
|
||||
/// Must be used on a struct with a field named `pool`, containing a `Pool<AsyncPgConnection>`.
|
||||
/// # Struct Attributes
|
||||
/// - table: Ident - The schema struct for the table
|
||||
/// - result: Type - The resulting model
|
||||
/// - create: Type - The insertable model
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// use diesel_async::{AsyncPgConnection, pooled_connection::deadpool::Pool};
|
||||
///
|
||||
/// #[derive(diesel_crud_derive::DieselCrudCreate)]
|
||||
/// #[diesel_crud(table = crate::schema::user, result = crate::models::User, create = crate::models::InsertUser)]
|
||||
/// struct TestServiceCreate {
|
||||
/// pool: Pool<AsyncPgConnection>,
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_derive(DieselCrudCreate, attributes(diesel_crud))]
|
||||
pub fn derive_diesel_crud_create(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let mut item = syn::parse_macro_input!(item as DeriveInput);
|
||||
let struct_attributes = extract_attributes(&mut item).unwrap();
|
||||
derive_diesel_crud_create_impl(&struct_attributes, &item.ident).into()
|
||||
}
|
||||
|
||||
/// Derives the read function for CRUD operations.
|
||||
/// Must be used on a struct with a field named `pool`, containing a `Pool<AsyncPgConnection>`.
|
||||
/// # Struct Attributes
|
||||
/// - table: Ident - The schema struct for the table
|
||||
/// - pk: Type - The primary key type
|
||||
/// - result: Type - The resulting model
|
||||
/// - pk_field (optional): Expr - The field to use as the primary key. Defaults to `id`
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// use diesel_async::{AsyncPgConnection, pooled_connection::deadpool::Pool};
|
||||
///
|
||||
/// #[derive(diesel_crud_derive::DieselCrudRead)]
|
||||
/// #[diesel_crud(table = crate::schema::user, result = crate::models::User, pk = String)]
|
||||
/// struct TestServiceRead {
|
||||
/// pool: Pool<AsyncPgConnection>,
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_derive(DieselCrudRead, attributes(diesel_crud))]
|
||||
pub fn derive_diesel_crud_read(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let mut item = syn::parse_macro_input!(item as DeriveInput);
|
||||
let struct_attributes = extract_attributes(&mut item).unwrap();
|
||||
derive_diesel_crud_read_impl(&struct_attributes, &item.ident).into()
|
||||
}
|
||||
|
||||
/// Derives the update function for CRUD operations.
|
||||
/// Must be used on a struct with a field named `pool`, containing a `Pool<AsyncPgConnection>`.
|
||||
/// # Struct Attributes
|
||||
/// - table: Ident - The schema struct for the table
|
||||
/// - update: Type - The update model
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// use diesel_async::{AsyncPgConnection, pooled_connection::deadpool::Pool};
|
||||
///
|
||||
/// #[derive(diesel_crud_derive::DieselCrudUpdate)]
|
||||
/// #[diesel_crud(table = crate::schema::user, update = crate::models::User)]
|
||||
/// struct TestServiceUpdate {
|
||||
/// pool: Pool<AsyncPgConnection>,
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_derive(DieselCrudUpdate, attributes(diesel_crud))]
|
||||
pub fn derive_diesel_crud_update(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let mut item = syn::parse_macro_input!(item as DeriveInput);
|
||||
let struct_attributes = extract_attributes(&mut item).unwrap();
|
||||
derive_diesel_crud_update_impl(&struct_attributes, &item.ident).into()
|
||||
}
|
||||
|
||||
/// Derives the delete function for CRUD operations.
|
||||
/// Must be used on a struct with a field named `pool`, containing a `Pool<AsyncPgConnection>`.
|
||||
/// # Struct Attributes
|
||||
/// - table: Ident - The schema struct for the table
|
||||
/// - pk: Type - The primary key type
|
||||
/// - pk_field (optional): Expr - The field to use as the primary key. Defaults to `id`
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// use diesel_async::{AsyncPgConnection, pooled_connection::deadpool::Pool};
|
||||
///
|
||||
/// #[derive(diesel_crud_derive::DieselCrudDelete)]
|
||||
/// #[diesel_crud(table = crate::schema::user, pk = String)]
|
||||
/// struct TestServiceDelete {
|
||||
/// pool: Pool<AsyncPgConnection>,
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_derive(DieselCrudDelete, attributes(diesel_crud))]
|
||||
pub fn derive_diesel_crud_delete(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let mut item = syn::parse_macro_input!(item as DeriveInput);
|
||||
let struct_attributes = extract_attributes(&mut item).unwrap();
|
||||
derive_diesel_crud_delete_impl(&struct_attributes, &item.ident).into()
|
||||
}
|
||||
|
||||
/// Derives the list function for CRUD operations.
|
||||
/// Must be used on a struct with a field named `pool`, containing a `Pool<AsyncPgConnection>`.
|
||||
/// # Struct Attributes
|
||||
/// - table: Ident - The schema struct for the table
|
||||
/// - result: Type - The resulting model
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// use diesel_async::{AsyncPgConnection, pooled_connection::deadpool::Pool};
|
||||
///
|
||||
/// #[derive(diesel_crud_derive::DieselCrudList)]
|
||||
/// #[diesel_crud(table = crate::schema::user, result = crate::models::User)]
|
||||
/// struct TestServiceList {
|
||||
/// pool: Pool<AsyncPgConnection>,
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_derive(DieselCrudList, attributes(diesel_crud))]
|
||||
pub fn derive_diesel_crud_list(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let mut item = syn::parse_macro_input!(item as DeriveInput);
|
||||
let struct_attributes = extract_attributes(&mut item).unwrap();
|
||||
derive_diesel_crud_list_impl(&struct_attributes, &item.ident).into()
|
||||
}
|
35
crates/diesel_crud_derive/src/list.rs
Normal file
35
crates/diesel_crud_derive/src/list.rs
Normal file
@ -0,0 +1,35 @@
|
||||
use proc_macro2::Ident;
|
||||
use quote::quote;
|
||||
use syn::Expr;
|
||||
|
||||
use crate::{common, StructAttributes};
|
||||
|
||||
pub(crate) fn derive_diesel_crud_list_impl(
|
||||
StructAttributes { table, entity, .. }: &StructAttributes,
|
||||
identifier: &Ident,
|
||||
) -> proc_macro2::TokenStream {
|
||||
let body = function_body(table);
|
||||
let return_type = common::return_type(quote! { Vec<Self::Entity> });
|
||||
|
||||
quote! {
|
||||
#[automatically_derived]
|
||||
impl lib::diesel_crud_trait::DieselCrudList for #identifier {
|
||||
type Entity = #entity;
|
||||
fn list<'a, 'b>(&'a self) -> #return_type
|
||||
where
|
||||
Self: Sync + 'a,
|
||||
'a: 'b
|
||||
{
|
||||
Box::pin(async move {
|
||||
#body
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn function_body(table: &Expr) -> proc_macro2::TokenStream {
|
||||
common::function_body(quote! {
|
||||
diesel_async::RunQueryDsl::get_results(#table::table::table(), &mut connection).await.map_err(Into::into)
|
||||
})
|
||||
}
|
38
crates/diesel_crud_derive/src/read.rs
Normal file
38
crates/diesel_crud_derive/src/read.rs
Normal file
@ -0,0 +1,38 @@
|
||||
use proc_macro2::Ident;
|
||||
use quote::quote;
|
||||
use syn::Expr;
|
||||
|
||||
use crate::{common, StructAttributes};
|
||||
|
||||
pub(crate) fn derive_diesel_crud_read_impl(
|
||||
StructAttributes {
|
||||
table, entity, pk, ..
|
||||
}: &StructAttributes,
|
||||
identifier: &Ident,
|
||||
) -> proc_macro2::TokenStream {
|
||||
let body = function_body(table);
|
||||
let return_type = common::return_type(quote! { Self::Entity });
|
||||
|
||||
quote! {
|
||||
#[automatically_derived]
|
||||
impl lib::diesel_crud_trait::DieselCrudRead for #identifier {
|
||||
type PK = #pk;
|
||||
type Entity = #entity;
|
||||
fn read<'a, 'b>(&'a self, pk: Self::PK) -> #return_type
|
||||
where
|
||||
Self: Sync + 'a,
|
||||
'a: 'b
|
||||
{
|
||||
Box::pin(async move {
|
||||
#body
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn function_body(table: &Expr) -> proc_macro2::TokenStream {
|
||||
common::function_body(quote! {
|
||||
diesel_async::RunQueryDsl::get_result(diesel::QueryDsl::find(#table::table::table(), pk), &mut connection).await.map_err(Into::into)
|
||||
})
|
||||
}
|
39
crates/diesel_crud_derive/src/update.rs
Normal file
39
crates/diesel_crud_derive/src/update.rs
Normal file
@ -0,0 +1,39 @@
|
||||
use crate::{common, StructAttributes};
|
||||
use proc_macro2::Ident;
|
||||
use quote::quote;
|
||||
use syn::Expr;
|
||||
|
||||
pub(crate) fn derive_diesel_crud_update_impl(
|
||||
StructAttributes { table, update, .. }: &StructAttributes,
|
||||
identifier: &Ident,
|
||||
) -> proc_macro2::TokenStream {
|
||||
let body = function_body(table);
|
||||
let return_type = common::return_type(quote! { usize });
|
||||
|
||||
quote! {
|
||||
#[automatically_derived]
|
||||
impl lib::diesel_crud_trait::DieselCrudUpdate for #identifier {
|
||||
type Update = #update;
|
||||
fn update<'a, 'b>(&'a self, update: Self::Update) -> #return_type
|
||||
where
|
||||
Self: Sync + 'a,
|
||||
'a: 'b,
|
||||
{
|
||||
Box::pin(async move {
|
||||
#body
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn function_body(table: &Expr) -> proc_macro2::TokenStream {
|
||||
common::function_body(quote! {
|
||||
diesel_async::RunQueryDsl::execute(
|
||||
diesel::dsl::update(#table::table::table()).set(update),
|
||||
&mut connection,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
})
|
||||
}
|
Reference in New Issue
Block a user