Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add serialize_fn attribute to Insertable derive macro #3837

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 41 additions & 9 deletions diesel_derives/src/as_changeset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,24 +61,33 @@ pub fn derive(item: DeriveInput) -> Result<TokenStream> {
None => treat_none_as_null,
};

match field.serialize_as.as_ref() {
Some(AttributeSpanWrapper { item: ty, .. }) => {
match (field.serialize_as.as_ref(), field.serialize_fn.as_ref()) {
(Some(AttributeSpanWrapper { item: ty, .. }), serialize_fn) => {
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,
)?);
if let Some(AttributeSpanWrapper { item: function, .. }) = serialize_fn {
direct_field_ty.push(field_changeset_expr_serialize_fn(
field,
table_name,
function,
treat_none_as_null,
)?);
} else {
direct_field_assign.push(field_changeset_expr_serialize_as(
field,
table_name,
ty,
treat_none_as_null,
)?);
}

generate_borrowed_changeset = false; // as soon as we hit one field with #[diesel(serialize_as)] there is no point in generating the impl of AsChangeset for borrowed structs
}
None => {
(None, None) => {
direct_field_ty.push(field_changeset_ty(
field,
table_name,
Expand All @@ -104,6 +113,12 @@ pub fn derive(item: DeriveInput) -> Result<TokenStream> {
treat_none_as_null,
)?);
}
(None, Some(AttributeSpanWrapper { attribute_span, .. })) => {
return Err(syn::Error::new(
*attribute_span,
"`#[diesel(serialize_fn)]` requires `#[diesel(serialize_as)]` to be declared as well",
));
}
}
}

Expand Down Expand Up @@ -222,3 +237,20 @@ fn field_changeset_expr_serialize_as(
Ok(quote!(#column.eq(::std::convert::Into::<#ty>::into(self.#field_name))))
}
}

fn field_changeset_expr_serialize_fn(
field: &Field,
table_name: &Path,
function: &Expr,
treat_none_as_null: bool,
) -> Result<TokenStream> {
let field_name = &field.name;
let column_name = field.column_name()?;
column_name.valid_ident()?;
let column: Expr = parse_quote!(#table_name::#column_name);
if !treat_none_as_null && is_option_ty(&field.ty) {
Ok(quote!(self.#field_name.map(|x| #column.eq((#function)(x)))))
} else {
Ok(quote!(#column.eq((#function)(self.#field_name))))
}
}
19 changes: 16 additions & 3 deletions diesel_derives/src/attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ use crate::deprecated::ParseDeprecated;
use crate::parsers::{BelongsTo, MysqlType, PostgresType, SqliteType};
use crate::util::{
parse_eq, parse_paren, unknown_attribute, BELONGS_TO_NOTE, COLUMN_NAME_NOTE,
DESERIALIZE_AS_NOTE, MYSQL_TYPE_NOTE, POSTGRES_TYPE_NOTE, SELECT_EXPRESSION_NOTE,
SELECT_EXPRESSION_TYPE_NOTE, SERIALIZE_AS_NOTE, SQLITE_TYPE_NOTE, SQL_TYPE_NOTE,
TABLE_NAME_NOTE, TREAT_NONE_AS_DEFAULT_VALUE_NOTE, TREAT_NONE_AS_NULL_NOTE,
DESERIALIZE_AS_NOTE, DESERIALIZE_FN_NOTE, MYSQL_TYPE_NOTE, POSTGRES_TYPE_NOTE,
SELECT_EXPRESSION_NOTE, SELECT_EXPRESSION_TYPE_NOTE, SERIALIZE_AS_NOTE, SERIALIZE_FN_NOTE,
SQLITE_TYPE_NOTE, SQL_TYPE_NOTE, TABLE_NAME_NOTE, TREAT_NONE_AS_DEFAULT_VALUE_NOTE,
TREAT_NONE_AS_NULL_NOTE,
};

use crate::util::{parse_paren_list, CHECK_FOR_BACKEND_NOTE};
Expand All @@ -40,6 +41,8 @@ pub enum FieldAttr {

SerializeAs(Ident, TypePath),
DeserializeAs(Ident, TypePath),
SerializeFn(Ident, Expr),
DeserializeFn(Ident, Expr),
SelectExpression(Ident, Expr),
SelectExpressionType(Ident, Type),
}
Expand Down Expand Up @@ -145,6 +148,14 @@ impl Parse for FieldAttr {
name,
parse_eq(input, DESERIALIZE_AS_NOTE)?,
)),
"serialize_fn" => Ok(FieldAttr::SerializeFn(
name,
parse_eq(input, SERIALIZE_FN_NOTE)?,
)),
"deserialize_fn" => Ok(FieldAttr::DeserializeFn(
name,
parse_eq(input, DESERIALIZE_FN_NOTE)?,
)),
"select_expression" => Ok(FieldAttr::SelectExpression(
name,
parse_eq(input, SELECT_EXPRESSION_NOTE)?,
Expand Down Expand Up @@ -179,6 +190,8 @@ impl MySpanned for FieldAttr {
| FieldAttr::TreatNoneAsDefaultValue(ident, _)
| FieldAttr::SerializeAs(ident, _)
| FieldAttr::DeserializeAs(ident, _)
| FieldAttr::SerializeFn(ident, _)
| FieldAttr::DeserializeFn(ident, _)
| FieldAttr::SelectExpression(ident, _)
| FieldAttr::SelectExpressionType(ident, _) => ident.span(),
}
Expand Down
20 changes: 20 additions & 0 deletions diesel_derives/src/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ pub struct Field {
pub treat_none_as_null: Option<AttributeSpanWrapper<bool>>,
pub serialize_as: Option<AttributeSpanWrapper<Type>>,
pub deserialize_as: Option<AttributeSpanWrapper<Type>>,
pub serialize_fn: Option<AttributeSpanWrapper<Expr>>,
pub deserialize_fn: Option<AttributeSpanWrapper<Expr>>,
pub select_expression: Option<AttributeSpanWrapper<Expr>>,
pub select_expression_type: Option<AttributeSpanWrapper<Type>>,
pub embed: Option<AttributeSpanWrapper<bool>>,
Expand All @@ -29,6 +31,8 @@ impl Field {
let mut sql_type = None;
let mut serialize_as = None;
let mut deserialize_as = None;
let mut serialize_fn = None;
let mut deserialize_fn = None;
let mut embed = None;
let mut select_expression = None;
let mut select_expression_type = None;
Expand Down Expand Up @@ -81,6 +85,20 @@ impl Field {
ident_span,
})
}
FieldAttr::SerializeFn(_, value) => {
serialize_fn = Some(AttributeSpanWrapper {
item: value,
attribute_span,
ident_span,
})
}
FieldAttr::DeserializeFn(_, value) => {
deserialize_fn = Some(AttributeSpanWrapper {
item: value,
attribute_span,
ident_span,
})
}
FieldAttr::SelectExpression(_, value) => {
select_expression = Some(AttributeSpanWrapper {
item: value,
Expand Down Expand Up @@ -125,6 +143,8 @@ impl Field {
treat_none_as_null,
serialize_as,
deserialize_as,
serialize_fn,
deserialize_fn,
select_expression,
select_expression_type,
embed,
Expand Down
73 changes: 61 additions & 12 deletions diesel_derives/src/insertable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,18 @@ fn derive_into_single_table(
None => treat_none_as_default_value,
};

match (field.serialize_as.as_ref(), field.embed()) {
(None, true) => {
match (
field.serialize_as.as_ref(),
field.serialize_fn.as_ref(),
field.embed(),
) {
(None, None, true) => {
direct_field_ty.push(field_ty_embed(field, None));
direct_field_assign.push(field_expr_embed(field, None));
ref_field_ty.push(field_ty_embed(field, Some(quote!(&'insert))));
ref_field_assign.push(field_expr_embed(field, Some(quote!(&))));
}
(None, false) => {
(None, None, false) => {
direct_field_ty.push(field_ty(
field,
table_name,
Expand All @@ -100,28 +104,50 @@ fn derive_into_single_table(
treat_none_as_default_value,
)?);
}
(Some(AttributeSpanWrapper { item: ty, .. }), false) => {
(Some(AttributeSpanWrapper { item: ty, .. }), serialize_fn, false) => {
direct_field_ty.push(field_ty_serialize_as(
field,
table_name,
ty,
treat_none_as_default_value,
)?);
direct_field_assign.push(field_expr_serialize_as(
field,
table_name,
ty,
treat_none_as_default_value,
)?);
if let Some(AttributeSpanWrapper { item: function, .. }) = serialize_fn {
direct_field_assign.push(field_expr_serialize_fn(
field,
table_name,
ty,
function,
treat_none_as_default_value,
)?);
} else {
direct_field_assign.push(field_expr_serialize_as(
field,
table_name,
ty,
treat_none_as_default_value,
)?);
}

generate_borrowed_insert = false; // as soon as we hit one field with #[diesel(serialize_as)] there is no point in generating the impl of Insertable for borrowed structs
}
(Some(AttributeSpanWrapper { attribute_span, .. }), true) => {
(Some(AttributeSpanWrapper { attribute_span, .. }), _, true) => {
return Err(syn::Error::new(
*attribute_span,
"`#[diesel(embed)]` cannot be combined with `#[diesel(serialize_as)]`",
));
}
(None, Some(AttributeSpanWrapper { attribute_span, .. }), true) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to have a compile_fail test for this cases.

return Err(syn::Error::new(
*attribute_span,
"`#[diesel(embed)]` cannot be combined with `#[diesel(serialize_fn)]`",
));
}
(None, Some(AttributeSpanWrapper { attribute_span, .. }), false) => {
return Err(syn::Error::new(
*attribute_span,
"`#[diesel(serialize_fn)]` requires `#[diesel(serialize_as)]` to be declared as well",
));
}
}
}

Expand Down Expand Up @@ -227,14 +253,37 @@ fn field_expr_serialize_as(
Ok(quote!(self.#field_name.map(|x| #column.eq(::std::convert::Into::<#ty>::into(x)))))
} else {
Ok(
quote!(std::option::Option::Some(#column.eq(::std::convert::Into::<#ty>::into(self.#field_name)))),
quote!(::std::option::Option::Some(#column.eq(::std::convert::Into::<#ty>::into(self.#field_name)))),
)
}
} else {
Ok(quote!(#column.eq(::std::convert::Into::<#ty>::into(self.#field_name))))
}
}

fn field_expr_serialize_fn(
field: &Field,
table_name: &Path,
ty: &Type,
function: &Expr,
treat_none_as_default_value: bool,
) -> Result<TokenStream> {
let field_name = &field.name;
let column_name = field.column_name()?;
column_name.valid_ident()?;
let column = quote!(#table_name::#column_name);

if treat_none_as_default_value {
if is_option_ty(ty) {
Ok(quote!(self.#field_name.map(|x| #column.eq((#function)(x)))))
} else {
Ok(quote!(::std::option::Option::Some(#column.eq((#function)(self.#field_name)))))
}
} else {
Ok(quote!(#column.eq((#function)(self.#field_name))))
}
}

fn field_ty(
field: &Field,
table_name: &Path,
Expand Down
2 changes: 2 additions & 0 deletions diesel_derives/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ pub const COLUMN_NAME_NOTE: &str = "column_name = foo";
pub const SQL_TYPE_NOTE: &str = "sql_type = Foo";
pub const SERIALIZE_AS_NOTE: &str = "serialize_as = Foo";
pub const DESERIALIZE_AS_NOTE: &str = "deserialize_as = Foo";
pub const SERIALIZE_FN_NOTE: &str = "serialize_fn = some_function";
pub const DESERIALIZE_FN_NOTE: &str = "deserialize_fn = some_function";
pub const TABLE_NAME_NOTE: &str = "table_name = foo";
pub const TREAT_NONE_AS_DEFAULT_VALUE_NOTE: &str = "treat_none_as_default_value = true";
pub const TREAT_NONE_AS_NULL_NOTE: &str = "treat_none_as_null = true";
Expand Down
Loading