use proc_macro2::TokenStream;
use syn::{DeriveInput, Expr, Path, Type};
use attrs::AttributeSpanWrapper;
use field::Field;
use model::Model;
use util::{inner_of_option_ty, is_option_ty, wrap_in_dummy_mod};
pub fn derive(item: DeriveInput) -> TokenStream {
let model = Model::from_item(&item, false);
let struct_name = &item.ident;
let table_name = model.table_name();
let fields_for_update = model
.fields()
.iter()
.filter(|f| {
!model
.primary_key_names
.iter()
.any(|p| f.column_name() == *p)
})
.collect::<Vec<_>>();
if fields_for_update.is_empty() {
abort_call_site!(
"Deriving `AsChangeset` on a structure that only contains primary keys isn't supported.";
help = "If you want to change the primary key of a row, you should do so with `.set(table::id.eq(new_id))`.";
note = "`#[derive(AsChangeset)]` never changes the primary key of a row.";
)
}
let treat_none_as_null = model.treat_none_as_null();
let (impl_generics, ty_generics, where_clause) = item.generics.split_for_impl();
let mut generate_borrowed_changeset = true;
let mut direct_field_ty = Vec::with_capacity(fields_for_update.len());
let mut direct_field_assign = Vec::with_capacity(fields_for_update.len());
let mut ref_field_ty = Vec::with_capacity(fields_for_update.len());
let mut ref_field_assign = Vec::with_capacity(fields_for_update.len());
for field in fields_for_update {
match field.serialize_as.as_ref() {
Some(AttributeSpanWrapper { item: ty, .. }) => {
direct_field_ty.push(field_changeset_ty_serialize_as(
field,
table_name,
ty,
treat_none_as_null,
));
direct_field_assign.push(field_changeset_expr_serialize_as(
field,
table_name,
ty,
treat_none_as_null,
));
generate_borrowed_changeset = false; }
None => {
direct_field_ty.push(field_changeset_ty(
field,
table_name,
None,
treat_none_as_null,
));
direct_field_assign.push(field_changeset_expr(
field,
table_name,
None,
treat_none_as_null,
));
ref_field_ty.push(field_changeset_ty(
field,
table_name,
Some(quote!(&'update)),
treat_none_as_null,
));
ref_field_assign.push(field_changeset_expr(
field,
table_name,
Some(quote!(&)),
treat_none_as_null,
));
}
}
}
let changeset_owned = quote! {
impl #impl_generics AsChangeset for #struct_name #ty_generics
#where_clause
{
type Target = #table_name::table;
type Changeset = <(#(#direct_field_ty,)*) as AsChangeset>::Changeset;
fn as_changeset(self) -> Self::Changeset {
(#(#direct_field_assign,)*).as_changeset()
}
}
};
let changeset_borrowed = if generate_borrowed_changeset {
let mut impl_generics = item.generics.clone();
impl_generics.params.push(parse_quote!('update));
let (impl_generics, _, _) = impl_generics.split_for_impl();
quote! {
impl #impl_generics AsChangeset for &'update #struct_name #ty_generics
#where_clause
{
type Target = #table_name::table;
type Changeset = <(#(#ref_field_ty,)*) as AsChangeset>::Changeset;
fn as_changeset(self) -> Self::Changeset {
(#(#ref_field_assign,)*).as_changeset()
}
}
}
} else {
quote! {}
};
wrap_in_dummy_mod(quote!(
use diesel::query_builder::AsChangeset;
use diesel::prelude::*;
#changeset_owned
#changeset_borrowed
))
}
fn field_changeset_ty(
field: &Field,
table_name: &Path,
lifetime: Option<TokenStream>,
treat_none_as_null: bool,
) -> TokenStream {
let column_name = field.column_name();
if !treat_none_as_null && is_option_ty(&field.ty) {
let field_ty = inner_of_option_ty(&field.ty);
quote!(std::option::Option<diesel::dsl::Eq<#table_name::#column_name, #lifetime #field_ty>>)
} else {
let field_ty = &field.ty;
quote!(diesel::dsl::Eq<#table_name::#column_name, #lifetime #field_ty>)
}
}
fn field_changeset_expr(
field: &Field,
table_name: &Path,
lifetime: Option<TokenStream>,
treat_none_as_null: bool,
) -> TokenStream {
let field_name = &field.name;
let column_name = field.column_name();
if !treat_none_as_null && is_option_ty(&field.ty) {
if lifetime.is_some() {
quote!(self.#field_name.as_ref().map(|x| #table_name::#column_name.eq(x)))
} else {
quote!(self.#field_name.map(|x| #table_name::#column_name.eq(x)))
}
} else {
quote!(#table_name::#column_name.eq(#lifetime self.#field_name))
}
}
fn field_changeset_ty_serialize_as(
field: &Field,
table_name: &Path,
ty: &Type,
treat_none_as_null: bool,
) -> TokenStream {
let column_name = field.column_name();
if !treat_none_as_null && is_option_ty(&field.ty) {
let inner_ty = inner_of_option_ty(ty);
quote!(std::option::Option<diesel::dsl::Eq<#table_name::#column_name, #inner_ty>>)
} else {
quote!(diesel::dsl::Eq<#table_name::#column_name, #ty>)
}
}
fn field_changeset_expr_serialize_as(
field: &Field,
table_name: &Path,
ty: &Type,
treat_none_as_null: bool,
) -> TokenStream {
let field_name = &field.name;
let column_name = field.column_name();
let column: Expr = parse_quote!(#table_name::#column_name);
if !treat_none_as_null && is_option_ty(&field.ty) {
quote!(self.#field_name.map(|x| #column.eq(::std::convert::Into::<#ty>::into(x))))
} else {
quote!(#column.eq(::std::convert::Into::<#ty>::into(self.#field_name)))
}
}