Skip to content

Commit

Permalink
Detect if serde_as is used on Option fields and add default to re…
Browse files Browse the repository at this point in the history
…store support for missing fields

A `with` or `deserialize_with` on an `Option` field removes the ability to allow missing fields.
This modification detects if the field is `Option` and the transformation is `Option<T>`. In this
case it is likely that the missing field behavior is desired.
A `no_default` can be added to supress this new behavior.

This commit does not include any documentation changes.

Closes #185
  • Loading branch information
jonasbb committed Jun 5, 2022
1 parent 53af356 commit e7f2f5e
Show file tree
Hide file tree
Showing 2 changed files with 228 additions and 65 deletions.
116 changes: 116 additions & 0 deletions serde_with/tests/serde_as/serde_as_macro.rs
Expand Up @@ -189,3 +189,119 @@ fn test_serde_as_macro_multiple_field_attributes() {
}"#]],
);
}

/// Ensure that `serde_as` applies `default` if both the field and the conversion are option.
#[test]
fn test_default_on_option() {
#[serde_as]
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
struct Data {
#[serde_as(as = "Option<DisplayFromStr>")]
a: Option<u32>,
}

is_equal(
Data { a: None },
expect![[r#"
{
"a": null
}"#]],
);
is_equal(
Data { a: Some(123) },
expect![[r#"
{
"a": "123"
}"#]],
);
check_deserialization(Data { a: None }, "{}");

#[serde_as]
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
struct DataNoDefault {
#[serde_as(as = "Option<DisplayFromStr>", no_default)]
a: Option<u32>,
}

is_equal(
DataNoDefault { a: None },
expect![[r#"
{
"a": null
}"#]],
);
is_equal(
DataNoDefault { a: Some(123) },
expect![[r#"
{
"a": "123"
}"#]],
);
check_error_deserialization::<DataNoDefault>(
"{}",
expect!["missing field `a` at line 1 column 2"],
);

fn default_555() -> Option<u32> {
Some(555)
}

#[serde_as]
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
struct DataExplicitDefault {
#[serde_as(as = "Option<DisplayFromStr>")]
#[serde(default = "default_555")]
a: Option<u32>,
}

is_equal(
DataExplicitDefault { a: None },
expect![[r#"
{
"a": null
}"#]],
);
is_equal(
DataExplicitDefault { a: Some(123) },
expect![[r#"
{
"a": "123"
}"#]],
);
check_deserialization(DataExplicitDefault { a: Some(555) }, "{}");

#[serde_as]
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
struct DataString {
#[serde_as(as = "NoneAsEmptyString")]
a: Option<String>,
}

is_equal(
DataString { a: None },
expect![[r#"
{
"a": ""
}"#]],
);
is_equal(
DataString {
a: Some("123".to_string()),
},
expect![[r#"
{
"a": "123"
}"#]],
);
check_deserialization(DataString { a: None }, r#"{"a": ""}"#);
check_deserialization(
DataString {
a: Some("555".to_string()),
},
r#"{"a": "555"}"#,
);
check_error_deserialization::<DataString>(
"{}",
expect!["missing field `a` at line 1 column 2"],
);
}
177 changes: 112 additions & 65 deletions serde_with_macros/src/lib.rs
Expand Up @@ -45,7 +45,10 @@ extern crate proc_macro;
mod utils;

use crate::utils::{split_with_de_lifetime, DeriveOptions, IteratorExt as _};
use darling::{util::Override, Error as DarlingError, FromField, FromMeta};
use darling::{
util::{Flag, Override},
Error as DarlingError, FromField, FromMeta,
};
use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::quote;
Expand Down Expand Up @@ -294,52 +297,47 @@ pub fn skip_serializing_none(_args: TokenStream, input: TokenStream) -> TokenStr

/// Add the skip_serializing_if annotation to each field of the struct
fn skip_serializing_none_add_attr_to_field(field: &mut Field) -> Result<(), String> {
if let Type::Path(path) = &field.ty {
if is_std_option(&path.path) {
let has_skip_serializing_if =
field_has_attribute(field, "serde", "skip_serializing_if");

// Remove the `serialize_always` attribute
let mut has_always_attr = false;
field.attrs.retain(|attr| {
let has_attr = attr.path.is_ident("serialize_always");
has_always_attr |= has_attr;
!has_attr
});

// Error on conflicting attributes
if has_always_attr && has_skip_serializing_if {
let mut msg = r#"The attributes `serialize_always` and `serde(skip_serializing_if = "...")` cannot be used on the same field"#.to_string();
if let Some(ident) = &field.ident {
msg += ": `";
msg += &ident.to_string();
msg += "`";
}
msg += ".";
return Err(msg);
}
if is_std_option(&field.ty) {
let has_skip_serializing_if = field_has_attribute(field, "serde", "skip_serializing_if");

// Do nothing if `skip_serializing_if` or `serialize_always` is already present
if has_skip_serializing_if || has_always_attr {
return Ok(());
}
// Remove the `serialize_always` attribute
let mut has_always_attr = false;
field.attrs.retain(|attr| {
let has_attr = attr.path.is_ident("serialize_always");
has_always_attr |= has_attr;
!has_attr
});

// Add the `skip_serializing_if` attribute
let attr = parse_quote!(
#[serde(skip_serializing_if = "Option::is_none")]
);
field.attrs.push(attr);
} else {
// Warn on use of `serialize_always` on non-Option fields
let has_attr = field
.attrs
.iter()
.any(|attr| attr.path.is_ident("serialize_always"));
if has_attr {
return Err(
"`serialize_always` may only be used on fields of type `Option`.".into(),
);
// Error on conflicting attributes
if has_always_attr && has_skip_serializing_if {
let mut msg = r#"The attributes `serialize_always` and `serde(skip_serializing_if = "...")` cannot be used on the same field"#.to_string();
if let Some(ident) = &field.ident {
msg += ": `";
msg += &ident.to_string();
msg += "`";
}
msg += ".";
return Err(msg);
}

// Do nothing if `skip_serializing_if` or `serialize_always` is already present
if has_skip_serializing_if || has_always_attr {
return Ok(());
}

// Add the `skip_serializing_if` attribute
let attr = parse_quote!(
#[serde(skip_serializing_if = "Option::is_none")]
);
field.attrs.push(attr);
} else {
// Warn on use of `serialize_always` on non-Option fields
let has_attr = field
.attrs
.iter()
.any(|attr| attr.path.is_ident("serialize_always"));
if has_attr {
return Err("`serialize_always` may only be used on fields of type `Option`.".into());
}
}
Ok(())
Expand All @@ -352,12 +350,39 @@ fn skip_serializing_none_add_attr_to_field(field: &mut Field) -> Result<(), Stri
/// * `Option`
/// * `std::option::Option`, with or without leading `::`
/// * `core::option::Option`, with or without leading `::`
fn is_std_option(path: &Path) -> bool {
(path.leading_colon.is_none() && path.segments.len() == 1 && path.segments[0].ident == "Option")
|| (path.segments.len() == 3
&& (path.segments[0].ident == "std" || path.segments[0].ident == "core")
&& path.segments[1].ident == "option"
&& path.segments[2].ident == "Option")
fn is_std_option(type_: &Type) -> bool {
match type_ {
Type::Array(_)
| Type::BareFn(_)
| Type::ImplTrait(_)
| Type::Infer(_)
| Type::Macro(_)
| Type::Never(_)
| Type::Ptr(_)
| Type::Reference(_)
| Type::Slice(_)
| Type::TraitObject(_)
| Type::Tuple(_)
| Type::Verbatim(_) => false,

Type::Group(syn::TypeGroup { elem, .. })
| Type::Paren(syn::TypeParen { elem, .. })
| Type::Path(syn::TypePath {
qself: Some(syn::QSelf { ty: elem, .. }),
..
}) => is_std_option(elem),

Type::Path(syn::TypePath { qself: None, path }) => {
(path.leading_colon.is_none()
&& path.segments.len() == 1
&& path.segments[0].ident == "Option")
|| (path.segments.len() == 3
&& (path.segments[0].ident == "std" || path.segments[0].ident == "core")
&& path.segments[1].ident == "option"
&& path.segments[2].ident == "Option")
}
_ => false,
}
}

/// Determine if the `field` has an attribute with given `namespace` and `name`
Expand Down Expand Up @@ -527,6 +552,7 @@ fn serde_as_add_attr_to_field(
r#as: Option<Type>,
deserialize_as: Option<Type>,
serialize_as: Option<Type>,
no_default: Flag,
}

impl SerdeAsOptions {
Expand All @@ -543,6 +569,7 @@ fn serde_as_add_attr_to_field(
serialize_with: Option<String>,

borrow: Option<Override<String>>,
default: Option<Override<String>>,
}

impl SerdeOptions {
Expand All @@ -551,6 +578,33 @@ fn serde_as_add_attr_to_field(
}
}

/// Emit a `borrow` annotation, if the replacement type requires borrowing.
fn emit_borrow_annotation(serde_options: &SerdeOptions, as_type: &Type, field: &mut Field) {
let type_borrowcow = &syn::parse_quote!(BorrowCow);
// If the field is not borrowed yet, check if we need to borrow it.
if serde_options.borrow.is_none() && has_type_embedded(as_type, type_borrowcow) {
let attr_borrow = parse_quote!(#[serde(borrow)]);
field.attrs.push(attr_borrow);
}
}

/// Emit a `default` annotation, if `as_type` and `field` are both `Option`.
fn emit_default_annotation(
serde_as_options: &SerdeAsOptions,
serde_options: &SerdeOptions,
as_type: &Type,
field: &mut Field,
) {
if !serde_as_options.no_default.is_present()
&& serde_options.default.is_none()
&& is_std_option(as_type)
&& is_std_option(&field.ty)
{
let attr_borrow = parse_quote!(#[serde(default)]);
field.attrs.push(attr_borrow);
}
}

// Check if there even is any `serde_as` attribute and exit early if not.
if !field
.attrs
Expand Down Expand Up @@ -583,27 +637,20 @@ fn serde_as_add_attr_to_field(
}

let type_same = &syn::parse_quote!(#serde_with_crate_path::Same);
let type_borrowcow = &syn::parse_quote!(BorrowCow);
if let Some(type_) = serde_as_options.r#as {
// If the field is not borrowed yet, check if we need to borrow it.
if serde_options.borrow.is_none() && has_type_embedded(&type_, type_borrowcow) {
let attr_borrow = parse_quote!(#[serde(borrow)]);
field.attrs.push(attr_borrow);
}
if let Some(type_) = &serde_as_options.r#as {
emit_borrow_annotation(&serde_options, type_, field);
emit_default_annotation(&serde_as_options, &serde_options, type_, field);

let replacement_type = replace_infer_type_with_type(type_, type_same);
let replacement_type = replace_infer_type_with_type(type_.clone(), type_same);
let attr_inner_tokens = quote!(#serde_with_crate_path::As::<#replacement_type>).to_string();
let attr = parse_quote!(#[serde(with = #attr_inner_tokens)]);
field.attrs.push(attr);
}
if let Some(type_) = serde_as_options.deserialize_as {
// If the field is not borrowed yet, check if we need to borrow it.
if serde_options.borrow.is_none() && has_type_embedded(&type_, type_borrowcow) {
let attr_borrow = parse_quote!(#[serde(borrow)]);
field.attrs.push(attr_borrow);
}
if let Some(type_) = &serde_as_options.deserialize_as {
emit_borrow_annotation(&serde_options, type_, field);
emit_default_annotation(&serde_as_options, &serde_options, type_, field);

let replacement_type = replace_infer_type_with_type(type_, type_same);
let replacement_type = replace_infer_type_with_type(type_.clone(), type_same);
let attr_inner_tokens =
quote!(#serde_with_crate_path::As::<#replacement_type>::deserialize).to_string();
let attr = parse_quote!(#[serde(deserialize_with = #attr_inner_tokens)]);
Expand Down

0 comments on commit e7f2f5e

Please sign in to comment.