Rewritten diesel_crud to be more flexible

This commit is contained in:
Martin Berg Alstad
2024-08-24 19:29:54 +02:00
parent 3318aacf7c
commit 17c81f4da1
15 changed files with 385 additions and 348 deletions

View File

@ -0,0 +1,73 @@
use crate::common::PrimaryKey;
use deluxe::{extract_attributes, ExtractAttributes};
use proc_macro2::Ident;
use quote::quote;
use std::collections::HashMap;
use syn::spanned::Spanned;
use syn::{Data, DeriveInput, Expr, Path, Type};
#[derive(ExtractAttributes)]
#[deluxe(attributes(diesel))]
pub(crate) struct DieselStructAttributes {
table_name: Option<Expr>,
#[deluxe(rest)]
_rest: HashMap<Path, Expr>,
}
#[derive(ExtractAttributes)]
#[deluxe(attributes(diesel_crud))]
pub(crate) struct StructAttributes {
table: Option<Expr>,
#[deluxe(default)]
insert: Option<Type>,
#[deluxe(default)]
update: Option<Type>,
}
#[derive(ExtractAttributes)]
#[deluxe(attributes(diesel_crud))]
pub(crate) struct FieldAttributes(#[allow(unused)] Expr);
pub(crate) struct Attributes {
pub struct_ident: Ident,
pub table: Expr,
pub insert: Type,
pub update: Type,
pub pk: Option<PrimaryKey>,
}
pub(crate) fn extract_attrs(ast: &mut DeriveInput) -> deluxe::Result<Attributes> {
let struct_attributes: StructAttributes = extract_attributes(ast)?;
let diesel_attributes: DieselStructAttributes = extract_attributes(ast)?;
Ok(Attributes {
struct_ident: ast.ident.clone(),
table: diesel_attributes.table_name.unwrap_or_else(|| {
struct_attributes
.table
.expect("Table name should be provided on either diesel or diesel_crud attribute")
}),
insert: struct_attributes
.insert
.unwrap_or_else(|| Type::Verbatim(quote! { Self })),
update: struct_attributes
.update
.unwrap_or_else(|| Type::Verbatim(quote! { Self })),
pk: extract_field_attrs(ast).ok(),
})
}
fn extract_field_attrs(ast: &mut DeriveInput) -> deluxe::Result<PrimaryKey> {
if let Data::Struct(data_struct) = &mut ast.data {
for field in data_struct.fields.iter_mut() {
if let Ok(FieldAttributes(_)) = extract_attributes(field) {
return Ok(PrimaryKey {
ident: field.ident.clone().unwrap(),
ty: field.ty.clone(),
});
}
}
} else {
return Err(deluxe::Error::new(ast.span(), "Expected a struct"));
};
Err(deluxe::Error::new(ast.span(), "Primary key not found"))
}

View File

@ -1,16 +1,10 @@
use proc_macro2::Ident;
use quote::quote;
use syn::Type;
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) struct PrimaryKey {
pub ident: Ident,
pub ty: Type,
}
pub(crate) fn return_type(output: proc_macro2::TokenStream) -> proc_macro2::TokenStream {

View File

@ -1,49 +1,35 @@
use crate::{common, StructAttributes};
use proc_macro2::Ident;
use crate::{common, Attributes};
use quote::quote;
use syn::Expr;
pub(crate) fn derive_diesel_crud_create_impl(
StructAttributes {
Attributes {
struct_ident,
table,
entity,
create,
insert,
..
}: &StructAttributes,
identifier: &Ident,
}: &Attributes,
) -> proc_macro2::TokenStream {
let body = function_body(table);
let return_type = common::return_type(quote! { Self::Entity });
let return_type = common::return_type(quote! { Self });
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
impl lib::diesel_crud_trait::DieselCrudCreate<#table::table> for #struct_ident {
type Insert = #insert;
fn create<'a, 'b>(insert: Self::Insert, conn: &'a mut diesel_async::AsyncPgConnection) -> #return_type
where
Self: Sync + 'a,
Self: Sized + Sync + 'a,
'a: 'b,
'insertable: 'b
{
Box::pin(async move {
#body
use diesel::associations::HasTable;
diesel_async::RunQueryDsl::get_result(
diesel::dsl::insert_into(#table::table::table()).values(insert),
conn
)
.await
.map_err(Into::into)
})
}
}
}
}
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)
})
}

View File

@ -1,55 +1,46 @@
use crate::{common, StructAttributes};
use proc_macro2::Ident;
use quote::{quote, ToTokens};
use syn::Expr;
use crate::{common, Attributes, PrimaryKey};
use quote::quote;
pub(crate) fn derive_diesel_crud_delete_impl(
StructAttributes {
Attributes {
struct_ident,
table,
pk,
pk_field,
..
}: &StructAttributes,
identifier: &Ident,
}: &Attributes,
) -> 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 });
if pk.is_none() {
panic!("Please specify a primary key using #[diesel_crud(pk)]");
}
let PrimaryKey {
ident: pk_ident,
ty: pk_type,
} = pk.as_ref().unwrap();
let return_type = common::return_type(quote! { Self });
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
impl lib::diesel_crud_trait::DieselCrudDelete for #struct_ident {
type PK = #pk_type;
fn delete<'a, 'b>(pk: Self::PK, conn: &'a mut diesel_async::AsyncPgConnection) -> #return_type
where
Self: Sync + 'a,
Self: Sized + Sync + 'a,
'a: 'b,
'pk: 'b,
{
Box::pin(async move {
#body
use diesel::QueryDsl;
use diesel::associations::HasTable;
diesel_async::RunQueryDsl::get_result(
diesel::delete(
#table::table
.filter(diesel::expression_methods::ExpressionMethods::eq(#table::#pk_ident, pk))
),
conn,
)
.await
.map_err(Into::into)
})
}
}
}
}
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)
})
}

View File

@ -1,14 +1,16 @@
extern crate proc_macro;
use crate::attributes::{extract_attrs, Attributes};
use crate::common::PrimaryKey;
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};
use syn::{parse_macro_input, DeriveInput};
mod attributes;
mod common;
mod create;
mod delete;
@ -16,24 +18,6 @@ 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
@ -43,16 +27,19 @@ pub(crate) struct StructAttributes {
#[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 attrs = extract_attrs(&mut item).unwrap();
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 create = derive_diesel_crud_create_impl(&attrs);
let read = derive_diesel_crud_read_impl(&attrs);
let update = derive_diesel_crud_update_impl(&attrs);
let delete = derive_diesel_crud_delete_impl(&attrs);
let list = derive_diesel_crud_list_impl(&attrs);
let table = struct_attributes.table;
let Attributes {
table,
struct_ident,
..
} = attrs;
let expanded = quote! {
#create
#read
@ -60,124 +47,119 @@ pub fn derive_diesel_crud(item: proc_macro::TokenStream) -> proc_macro::TokenStr
#delete
#list
impl<'insertable, 'entity> lib::diesel_crud_trait::DieselCrud<'insertable, 'entity, #table::table> for #identifier
where
'entity: 'insertable
{}
impl lib::diesel_crud_trait::DieselCrud<#table::table> for #struct_ident {}
};
expanded.into()
}
/// Derives the create function for CRUD operations.
/// Must be used on a struct with a field named `pool`, containing a `Pool<AsyncPgConnection>`.
/// Must be used on a struct.
/// # Struct Attributes
/// - table: Ident - The schema struct for the table
/// - result: Type - The resulting model
/// - create: Type - The insertable model
/// - table: Expr - The schema struct for the table (can be provided on either diesel or diesel_crud attribute)
/// - insert: Type - The insertable model (Optional, defaults to `Self`)
/// # 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>,
/// #[derive(Queryable, diesel_crud_derive::DieselCrudCreate)]
/// #[diesel_crud(create = crate::models::InsertUser)]
/// #[diesel(table_name = crate::schema::user)]
/// struct User {
/// #[diesel_crud(pk)]
/// email: String,
/// password: String,
/// }
/// ```
#[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()
let attrs = extract_attrs(&mut item).unwrap();
derive_diesel_crud_create_impl(&attrs).into()
}
/// Derives the read function for CRUD operations.
/// Must be used on a struct with a field named `pool`, containing a `Pool<AsyncPgConnection>`.
/// Must be used on a struct with one field marked as the primary key.
/// # 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`
/// - table: Expr - The schema struct for the table (can be provided on either diesel or diesel_crud attribute)
/// # Field Attributes
/// - pk: Ident - The primary key field (Only one field should be marked as the primary key)
/// # 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>,
/// #[derive(Queryable, diesel_crud_derive::DieselCrudRead)]
/// #[diesel(table_name = crate::schema::user)]
/// struct User {
/// #[diesel_crud(pk)]
/// email: String,
/// password: String,
/// }
/// ```
#[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()
let attrs = extract_attrs(&mut item).unwrap();
derive_diesel_crud_read_impl(&attrs).into()
}
/// Derives the update function for CRUD operations.
/// Must be used on a struct with a field named `pool`, containing a `Pool<AsyncPgConnection>`.
/// Must be used on a struct.
/// # Struct Attributes
/// - table: Ident - The schema struct for the table
/// - update: Type - The update model
/// - table: Expr - The schema struct for the table (can be provided on either diesel or diesel_crud attribute)
/// - update: Type - The update model (Optional, defaults to `Self`)
/// # 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>,
/// #[derive(Queryable, diesel_crud_derive::DieselCrudUpdate)]
/// #[diesel(table_name = crate::schema::user)]
/// struct User {
/// #[diesel_crud(pk)]
/// email: String,
/// password: String,
/// }
/// ```
#[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()
let attrs = extract_attrs(&mut item).unwrap();
derive_diesel_crud_update_impl(&attrs).into()
}
/// Derives the delete function for CRUD operations.
/// Must be used on a struct with a field named `pool`, containing a `Pool<AsyncPgConnection>`.
/// Must be used on a struct with a field marked as primary key.
/// # 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`
/// - table: Expr - The schema struct for the table (can be provided on either diesel or diesel_crud attribute)
/// # Field Attributes
/// - pk: Ident - The primary key field (Only one field should be marked as the primary key)
/// # 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>,
/// #[derive(Queryable, diesel_crud_derive::DieselCrudDelete)]
/// #[diesel(table_name = crate::schema::user)]
/// struct User {
/// #[diesel_crud(pk)]
/// email: String,
/// password: String,
/// }
/// ```
#[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()
let attrs = extract_attrs(&mut item).unwrap();
derive_diesel_crud_delete_impl(&attrs).into()
}
/// Derives the list function for CRUD operations.
/// Must be used on a struct with a field named `pool`, containing a `Pool<AsyncPgConnection>`.
/// Must be used on a struct.
/// # Struct Attributes
/// - table: Ident - The schema struct for the table
/// - result: Type - The resulting model
/// - table: Expr - The schema struct for the table (can be provided on either diesel or diesel_crud attribute)
/// # 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>,
/// #[derive(Queryable, diesel_crud_derive::DieselCrudList)]
/// #[diesel(table_name = crate::schema::user)]
/// struct User {
/// #[diesel_crud(pk)]
/// email: String,
/// password: String,
/// }
/// ```
#[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()
let attrs = extract_attrs(&mut item).unwrap();
derive_diesel_crud_list_impl(&attrs).into()
}

View File

@ -1,35 +1,28 @@
use proc_macro2::Ident;
use crate::{common, Attributes};
use quote::quote;
use syn::Expr;
use crate::{common, StructAttributes};
pub(crate) fn derive_diesel_crud_list_impl(
StructAttributes { table, entity, .. }: &StructAttributes,
identifier: &Ident,
Attributes {
struct_ident,
table,
..
}: &Attributes,
) -> proc_macro2::TokenStream {
let body = function_body(table);
let return_type = common::return_type(quote! { Vec<Self::Entity> });
let return_type = common::return_type(quote! { Vec<Self> });
quote! {
#[automatically_derived]
impl lib::diesel_crud_trait::DieselCrudList for #identifier {
type Entity = #entity;
fn list<'a, 'b>(&'a self) -> #return_type
impl lib::diesel_crud_trait::DieselCrudList for #struct_ident {
fn list<'a, 'b>(conn: &'a mut diesel_async::AsyncPgConnection) -> #return_type
where
Self: Sync + 'a,
Self: Sized + Sync + 'a,
'a: 'b
{
Box::pin(async move {
#body
use diesel::associations::HasTable;
diesel_async::RunQueryDsl::get_results(#table::table::table(), conn).await.map_err(Into::into)
})
}
}
}
}
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)
})
}

View File

@ -1,38 +1,40 @@
use proc_macro2::Ident;
use crate::common::PrimaryKey;
use crate::{common, Attributes};
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,
Attributes {
struct_ident,
table,
pk,
..
}: &Attributes,
) -> proc_macro2::TokenStream {
let body = function_body(table);
let return_type = common::return_type(quote! { Self::Entity });
if pk.is_none() {
panic!("Please specify a primary key using #[diesel_crud(pk)]");
}
let PrimaryKey { ty: pk_type, .. } = pk.as_ref().unwrap();
let return_type = common::return_type(quote! { Self });
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
impl lib::diesel_crud_trait::DieselCrudRead for #struct_ident {
type PK = #pk_type;
fn read<'a, 'b>(pk: Self::PK, conn: &'a mut diesel_async::AsyncPgConnection) -> #return_type
where
Self: Sync + 'a,
Self: Sized + Sync + 'a,
'a: 'b
{
Box::pin(async move {
#body
use diesel::associations::HasTable;
diesel_async::RunQueryDsl::get_result(
diesel::QueryDsl::find(#table::table::table(), pk),
conn
)
.await
.map_err(Into::into)
})
}
}
}
}
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)
})
}

View File

@ -1,39 +1,35 @@
use crate::{common, StructAttributes};
use proc_macro2::Ident;
use crate::{common, Attributes};
use quote::quote;
use syn::Expr;
pub(crate) fn derive_diesel_crud_update_impl(
StructAttributes { table, update, .. }: &StructAttributes,
identifier: &Ident,
Attributes {
struct_ident,
table,
update,
..
}: &Attributes,
) -> proc_macro2::TokenStream {
let body = function_body(table);
let return_type = common::return_type(quote! { usize });
let return_type = common::return_type(quote! { Self });
quote! {
#[automatically_derived]
impl lib::diesel_crud_trait::DieselCrudUpdate for #identifier {
impl lib::diesel_crud_trait::DieselCrudUpdate for #struct_ident {
type Update = #update;
fn update<'a, 'b>(&'a self, update: Self::Update) -> #return_type
fn update<'a, 'b>(update: Self::Update, conn: &'a mut diesel_async::AsyncPgConnection) -> #return_type
where
Self: Sync + 'a,
Self: Sized + Sync + 'a,
'a: 'b,
{
Box::pin(async move {
#body
use diesel::associations::HasTable;
diesel_async::RunQueryDsl::get_result(
diesel::dsl::update(#table::table::table()).set(update),
conn,
)
.await
.map_err(Into::into)
})
}
}
}
}
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)
})
}

View File

@ -1,18 +1,19 @@
mod error;
pub use error::CrudError;
use async_trait::async_trait;
use diesel::{AsChangeset, Insertable};
use diesel_async::AsyncPgConnection;
pub use error::CrudError;
pub trait DieselCrud<'insertable, 'entity, Table>:
DieselCrudCreate<'insertable, 'entity, Table>
+ DieselCrudRead
+ DieselCrudUpdate
+ DieselCrudDelete
+ DieselCrudList
where
'entity: 'insertable,
/// Combines all CRUD operations into a single trait
/// Includes:
/// - Create
/// - Read
/// - Update
/// - Delete
/// - List
pub trait DieselCrud<Table>:
DieselCrudCreate<Table> + DieselCrudRead + DieselCrudUpdate + DieselCrudDelete + DieselCrudList
{
}
@ -21,20 +22,19 @@ where
///
/// Implementing the trait requires the `async_trait` macro.
/// # Associations
/// - `Create` - The type to insert
/// - `Entity` - The type that will be returned
/// - `Insert` - The type to insert, must implement `Insertable<Table>`
/// # Parameters
/// - `create` - The entity to insert
/// - `insert` - The entity to insert
/// - `conn` - The database connection
/// # Returns
/// A result containing the inserted entity or a `CrudError`
#[async_trait]
pub trait DieselCrudCreate<'insertable, 'entity, Table>
pub trait DieselCrudCreate<Table>
where
'entity: 'insertable,
Self: Sized,
{
type Create: Insertable<Table>;
type Entity: 'entity;
async fn create(&self, create: &'insertable Self::Create) -> Result<Self::Entity, CrudError>;
type Insert: Insertable<Table>;
async fn create(insert: Self::Insert, conn: &mut AsyncPgConnection) -> Result<Self, CrudError>;
}
/// Gets an entity from the database
@ -42,17 +42,19 @@ where
/// Implementing the trait requires the `async_trait` macro.
/// # Associations
/// - `PK` - The primary key of the entity
/// - `Entity` - The type that will be returned
/// # Parameters
/// - `pk` - The primary key of the entity
/// - `conn` - The database connection
/// # Returns
/// A result containing the entity or a `CrudError`.
/// If the entity is not found, the error should be `CrudError::NotFound`.
#[async_trait]
pub trait DieselCrudRead {
pub trait DieselCrudRead
where
Self: Sized,
{
type PK;
type Entity;
async fn read(&self, pk: Self::PK) -> Result<Self::Entity, CrudError>;
async fn read(pk: Self::PK, conn: &mut AsyncPgConnection) -> Result<Self, CrudError>;
}
/// Updates an entity in the database
@ -63,13 +65,17 @@ pub trait DieselCrudRead {
/// - `Update` - The type to update
/// # Parameters
/// - `update` - The update to apply
/// - `conn` - The database connection
/// # Returns
/// A result containing the number of rows updated or a `CrudError`.
/// A result containing the old entry of the entity if successful or a `CrudError`.
/// If the entity is not found, the error should be `CrudError::NotFound`.
#[async_trait]
pub trait DieselCrudUpdate {
pub trait DieselCrudUpdate
where
Self: Sized,
{
type Update: AsChangeset;
async fn update(&self, update: Self::Update) -> Result<usize, CrudError>;
async fn update(update: Self::Update, conn: &mut AsyncPgConnection) -> Result<Self, CrudError>;
}
/// Deletes an entity from the database
@ -79,24 +85,30 @@ pub trait DieselCrudUpdate {
/// - `PK` - The primary key of the entity
/// # Parameters
/// - `pk` - The primary key of the entity
/// - `conn` - The database connection
/// # Returns
/// A result containing the number of rows deleted or a `CrudError`.
/// A result containing the deleted entity or a `CrudError`.
/// If the entity is not found, the error should be `CrudError::NotFound`.
#[async_trait]
pub trait DieselCrudDelete {
pub trait DieselCrudDelete
where
Self: Sized,
{
type PK;
async fn delete(&self, pk: &Self::PK) -> Result<usize, CrudError>;
async fn delete(pk: Self::PK, conn: &mut AsyncPgConnection) -> Result<Self, CrudError>;
}
/// Lists all entities in the table
///
/// Implementing the trait requires the `async_trait` macro.
/// # Associations
/// - `Entity` - The type that will be returned in a Vec
/// # Parameters
/// - `conn` - The database connection
/// # Returns
/// A result containing a Vec of entities or a `CrudError`.
#[async_trait]
pub trait DieselCrudList {
type Entity;
async fn list(&self) -> Result<Vec<Self::Entity>, CrudError>;
pub trait DieselCrudList
where
Self: Sized,
{
async fn list(conn: &mut AsyncPgConnection) -> Result<Vec<Self>, CrudError>;
}

View File

@ -10,3 +10,7 @@ homepage.workspace = true
diesel = { workspace = true }
diesel-async = { workspace = true }
lib = { path = "../../../lib", features = ["diesel", "derive"] }
[dev-dependencies]
tokio = { version = "1.39", features = ["macros"] }
dotenvy_macro = "0.15"

View File

@ -1,11 +1,8 @@
#![allow(unused)]
use diesel::{AsChangeset, Insertable, Queryable, Selectable};
use diesel_async::pooled_connection::deadpool::Pool;
use diesel_async::pooled_connection::AsyncDieselConnectionManager;
use diesel_async::AsyncPgConnection;
use diesel_async::{AsyncConnection, AsyncPgConnection};
use dotenvy_macro::dotenv;
use lib::diesel_crud_derive::{
DieselCrud, DieselCrudCreate, DieselCrudDelete, DieselCrudList, DieselCrudRead,
DieselCrudUpdate,
DieselCrudCreate, DieselCrudDelete, DieselCrudList, DieselCrudRead, DieselCrudUpdate,
};
use lib::diesel_crud_trait::DieselCrudCreate;
@ -16,67 +13,40 @@ diesel::table! {
}
}
#[derive(Queryable, Selectable, Insertable, AsChangeset)]
#[derive(
Queryable,
Selectable,
Insertable,
AsChangeset,
DieselCrudCreate,
DieselCrudDelete,
DieselCrudList,
DieselCrudRead,
DieselCrudUpdate,
)]
#[diesel_crud(insert = InsertUser)]
#[diesel(table_name = user)]
struct User {
#[diesel_crud(pk)]
email: String,
}
#[derive(Insertable)]
#[derive(Clone, Insertable)]
#[diesel(table_name = user)]
struct InsertUser {
email: String,
}
#[derive(DieselCrud)]
#[diesel_crud(table = user, entity = User, pk = String, pk_field = email, create = InsertUser, update = User)]
struct TestService {
pool: Pool<AsyncPgConnection>,
}
#[derive(DieselCrudCreate, DieselCrudRead, DieselCrudUpdate, DieselCrudDelete, DieselCrudList)]
#[diesel_crud(table = user, entity = User, pk = String, pk_field = email, create = InsertUser, update = User)]
struct TestServiceSeparate {
pool: Pool<AsyncPgConnection>,
}
#[derive(DieselCrudCreate)]
#[diesel_crud(table = user, entity = User, create = InsertUser)]
struct TestServiceCreate {
pool: Pool<AsyncPgConnection>,
}
#[derive(DieselCrudRead)]
#[diesel_crud(table = user, entity = User, pk = String)]
struct TestServiceRead {
pool: Pool<AsyncPgConnection>,
}
#[derive(DieselCrudUpdate)]
#[diesel_crud(table = user, update = User)]
struct TestServiceUpdate {
pool: Pool<AsyncPgConnection>,
}
#[derive(DieselCrudDelete)]
#[diesel_crud(table = user, pk = String, pk_field = email)]
struct TestServiceDelete {
pool: Pool<AsyncPgConnection>,
}
#[derive(DieselCrudList)]
#[diesel_crud(table = user, entity = User)]
struct TestServiceList {
pool: Pool<AsyncPgConnection>,
}
#[test]
fn test_insert_user() {
let config = AsyncDieselConnectionManager::<AsyncPgConnection>::new("");
let pool = Pool::builder(config).max_size(10).build().unwrap();
let service = TestServiceCreate { pool };
service.create(&InsertUser {
email: "test".to_string(),
});
#[tokio::test]
async fn test_insert_user() {
let database_url = dotenv!("DATABASE_URL");
let mut conn = AsyncPgConnection::establish(database_url).await.unwrap();
conn.begin_test_transaction().await.unwrap();
let _user = User::create(
InsertUser {
email: "test".to_string(),
},
&mut conn,
)
.await;
}