#![forbid(unsafe_code)]
#![warn(missing_copy_implementations, missing_debug_implementations)]
#![doc(test(attr(
// Problematic handling for foreign From<T> impls in tests
// https://github.com/rust-lang/rust/issues/121621
allow(unknown_lints, non_local_definitions),
deny(
missing_debug_implementations,
rust_2018_idioms,
trivial_casts,
trivial_numeric_casts,
unused_extern_crates,
unused_import_braces,
unused_qualifications,
warnings,
),
forbid(unsafe_code),
)))]
#![doc(test(no_crate_inject))]
#![doc(html_root_url = "https://docs.rs/serde_with_macros/3.14.0/")]
#![cfg(not(tarpaulin_include))]
mod apply;
mod lazy_bool;
mod utils;
use crate::utils::{
split_with_de_lifetime, DeriveOptions, IteratorExt as _, SchemaFieldCondition,
SchemaFieldConfig,
};
use darling::{
ast::NestedMeta,
util::{Flag, Override},
Error as DarlingError, FromField, FromMeta,
};
use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::quote;
use syn::{
parse::Parser,
parse_macro_input, parse_quote,
punctuated::{Pair, Punctuated},
spanned::Spanned,
DeriveInput, Error, Field, Fields, GenericArgument, ItemEnum, ItemStruct, Meta, Path,
PathArguments, ReturnType, Token, Type,
};
fn apply_function_to_struct_and_enum_fields<F>(
input: TokenStream,
function: F,
) -> Result<TokenStream2, Error>
where
F: Copy,
F: Fn(&mut Field) -> Result<(), String>,
{
fn apply_on_fields<F>(fields: &mut Fields, function: F) -> Result<(), Error>
where
F: Fn(&mut Field) -> Result<(), String>,
{
match fields {
Fields::Unit => Ok(()),
Fields::Named(fields) => fields
.named
.iter_mut()
.map(|field| function(field).map_err(|err| Error::new(field.span(), err)))
.collect_error(),
Fields::Unnamed(fields) => fields
.unnamed
.iter_mut()
.map(|field| function(field).map_err(|err| Error::new(field.span(), err)))
.collect_error(),
}
}
if let Ok(mut input) = syn::parse::<ItemStruct>(input.clone()) {
apply_on_fields(&mut input.fields, function)?;
Ok(quote!(#input))
} else if let Ok(mut input) = syn::parse::<ItemEnum>(input) {
input
.variants
.iter_mut()
.map(|variant| apply_on_fields(&mut variant.fields, function))
.collect_error()?;
Ok(quote!(#input))
} else {
Err(Error::new(
Span::call_site(),
"The attribute can only be applied to struct or enum definitions.",
))
}
}
fn apply_function_to_struct_and_enum_fields_darling<F>(
input: TokenStream,
serde_with_crate_path: &Path,
function: F,
) -> Result<TokenStream2, DarlingError>
where
F: Copy,
F: Fn(&mut Field) -> Result<(), DarlingError>,
{
fn apply_on_fields<F>(fields: &mut Fields, function: F) -> Result<(), DarlingError>
where
F: Fn(&mut Field) -> Result<(), DarlingError>,
{
match fields {
Fields::Unit => Ok(()),
Fields::Named(ref mut fields) => {
let errors: Vec<DarlingError> = fields
.named
.iter_mut()
.map(|field| function(field).map_err(|err| err.with_span(&field)))
.filter_map(std::result::Result::err)
.collect();
if errors.is_empty() {
Ok(())
} else {
Err(DarlingError::multiple(errors))
}
}
Fields::Unnamed(fields) => {
let errors: Vec<DarlingError> = fields
.unnamed
.iter_mut()
.map(|field| function(field).map_err(|err| err.with_span(&field)))
.filter_map(std::result::Result::err)
.collect();
if errors.is_empty() {
Ok(())
} else {
Err(DarlingError::multiple(errors))
}
}
}
}
let consume_serde_as_attribute = parse_quote!(
#[derive(#serde_with_crate_path::__private_consume_serde_as_attributes)]
);
if let Ok(mut input) = syn::parse::<ItemStruct>(input.clone()) {
apply_on_fields(&mut input.fields, function)?;
input.attrs.push(consume_serde_as_attribute);
Ok(quote!(#input))
} else if let Ok(mut input) = syn::parse::<ItemEnum>(input) {
let mut errors: Vec<DarlingError> = input
.variants
.iter()
.flat_map(|variant| {
variant.attrs.iter().filter_map(|attr| {
if attr.path().is_ident("serde_as") {
Some(
DarlingError::custom(
"serde_as attribute is not allowed on enum variants",
)
.with_span(&attr),
)
} else {
None
}
})
})
.collect();
errors.extend(
input
.variants
.iter_mut()
.map(|variant| apply_on_fields(&mut variant.fields, function))
.filter_map(std::result::Result::err),
);
if errors.is_empty() {
input.attrs.push(consume_serde_as_attribute);
Ok(quote!(#input))
} else {
Err(DarlingError::multiple(errors))
}
} else {
Err(DarlingError::custom(
"The attribute can only be applied to struct or enum definitions.",
)
.with_span(&Span::call_site()))
}
}
#[proc_macro_attribute]
pub fn skip_serializing_none(_args: TokenStream, input: TokenStream) -> TokenStream {
let res =
apply_function_to_struct_and_enum_fields(input, skip_serializing_none_add_attr_to_field)
.unwrap_or_else(|err| err.to_compile_error());
TokenStream::from(res)
}
fn skip_serializing_none_add_attr_to_field(field: &mut Field) -> Result<(), String> {
if is_std_option(&field.ty) {
let has_skip_serializing_if = field_has_attribute(field, "serde", "skip_serializing_if");
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
});
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 has_skip_serializing_if || has_always_attr {
return Ok(());
}
let attr = parse_quote!(
#[serde(skip_serializing_if = "Option::is_none")]
);
field.attrs.push(attr);
} else {
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(())
}
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,
}
}
fn field_has_attribute(field: &Field, namespace: &str, name: &str) -> bool {
for attr in &field.attrs {
if attr.path().is_ident(namespace) {
if let Meta::List(expr) = &attr.meta {
let nested = match Punctuated::<Meta, Token![,]>::parse_terminated
.parse2(expr.tokens.clone())
{
Ok(nested) => nested,
Err(_) => continue,
};
for expr in nested {
match expr {
Meta::NameValue(expr) => {
if let Some(ident) = expr.path.get_ident() {
if *ident == name {
return true;
}
}
}
Meta::Path(expr) => {
if let Some(ident) = expr.get_ident() {
if *ident == name {
return true;
}
}
}
_ => (),
}
}
}
}
}
false
}
#[proc_macro_attribute]
pub fn serde_as(args: TokenStream, input: TokenStream) -> TokenStream {
#[derive(FromMeta)]
struct SerdeContainerOptions {
#[darling(rename = "crate")]
alt_crate_path: Option<Path>,
#[darling(rename = "schemars")]
enable_schemars_support: Option<bool>,
}
match NestedMeta::parse_meta_list(args.into()) {
Ok(list) => {
let container_options = match SerdeContainerOptions::from_list(&list) {
Ok(v) => v,
Err(e) => {
return TokenStream::from(e.write_errors());
}
};
let serde_with_crate_path = container_options
.alt_crate_path
.unwrap_or_else(|| syn::parse_quote!(::serde_with));
let schemars_config = match container_options.enable_schemars_support {
_ if cfg!(not(any(
feature = "schemars_0_8",
feature = "schemars_0_9",
feature = "schemars_1"
))) =>
{
SchemaFieldConfig::False
}
Some(condition) => condition.into(),
None => utils::has_derive_jsonschema(input.clone()).unwrap_or_default(),
};
let res = apply_function_to_struct_and_enum_fields_darling(
input,
&serde_with_crate_path,
|field| serde_as_add_attr_to_field(field, &serde_with_crate_path, &schemars_config),
)
.unwrap_or_else(darling::Error::write_errors);
TokenStream::from(res)
}
Err(e) => TokenStream::from(DarlingError::from(e).write_errors()),
}
}
fn serde_as_add_attr_to_field(
field: &mut Field,
serde_with_crate_path: &Path,
schemars_config: &SchemaFieldConfig,
) -> Result<(), DarlingError> {
#[derive(FromField)]
#[darling(attributes(serde_as))]
struct SerdeAsOptions {
ty: Type,
r#as: Option<Type>,
deserialize_as: Option<Type>,
serialize_as: Option<Type>,
no_default: Flag,
}
impl SerdeAsOptions {
fn has_any_set(&self) -> bool {
self.r#as.is_some() || self.deserialize_as.is_some() || self.serialize_as.is_some()
}
}
#[derive(FromField)]
#[darling(attributes(serde), allow_unknown_fields)]
struct SerdeOptions {
with: Option<String>,
deserialize_with: Option<String>,
serialize_with: Option<String>,
borrow: Option<Override<String>>,
default: Option<Override<String>>,
}
impl SerdeOptions {
fn has_any_set(&self) -> bool {
self.with.is_some() || self.deserialize_with.is_some() || self.serialize_with.is_some()
}
}
fn emit_borrow_annotation(serde_options: &SerdeOptions, as_type: &Type, field: &mut Field) {
let type_borrowcow = &syn::parse_quote!(BorrowCow);
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);
}
}
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);
}
}
let mut has_serde_as = false;
field.attrs.iter_mut().for_each(|attr| {
if attr.path().is_ident("serde_as") {
has_serde_as = true;
if let Meta::List(metalist) = &mut attr.meta {
metalist.tokens = std::mem::take(&mut metalist.tokens)
.into_iter()
.map(|token| {
use proc_macro2::{Ident, TokenTree};
match token {
TokenTree::Ident(ident) if ident == "as" => {
TokenTree::Ident(Ident::new_raw("as", ident.span()))
}
_ => token,
}
})
.collect();
}
}
});
if !has_serde_as {
return Ok(());
}
let serde_as_options = SerdeAsOptions::from_field(field)?;
let serde_options = SerdeOptions::from_field(field)?;
let mut errors = Vec::new();
if !serde_as_options.has_any_set() {
errors.push(DarlingError::custom("An empty `serde_as` attribute on a field has no effect. You are missing an `as`, `serialize_as`, or `deserialize_as` parameter."));
}
if serde_as_options.has_any_set() && serde_options.has_any_set() {
errors.push(DarlingError::custom("Cannot combine `serde_as` with serde's `with`, `deserialize_with`, or `serialize_with`."));
}
if serde_as_options.r#as.is_some() && serde_as_options.deserialize_as.is_some() {
errors.push(DarlingError::custom("Cannot combine `as` with `deserialize_as`. Use `serialize_as` to specify different serialization code."));
} else if serde_as_options.r#as.is_some() && serde_as_options.serialize_as.is_some() {
errors.push(DarlingError::custom("Cannot combine `as` with `serialize_as`. Use `deserialize_as` to specify different deserialization code."));
}
if !errors.is_empty() {
return Err(DarlingError::multiple(errors));
}
let type_original = &serde_as_options.ty;
let type_same = &syn::parse_quote!(#serde_with_crate_path::Same);
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_.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);
match schemars_config {
SchemaFieldConfig::False => {}
lhs => {
let rhs = utils::schemars_with_attr_if(
&field.attrs,
&["with", "serialize_with", "deserialize_with", "schema_with"],
)?;
match lhs & !rhs {
SchemaFieldConfig::False => {}
condition => {
let attr_inner_tokens = quote! {
#serde_with_crate_path::Schema::<#type_original, #replacement_type>
};
let attr_inner_tokens = attr_inner_tokens.to_string();
let attr = match condition {
SchemaFieldConfig::False => unreachable!(),
SchemaFieldConfig::True => {
parse_quote! { #[schemars(with = #attr_inner_tokens)] }
}
SchemaFieldConfig::Lazy(SchemaFieldCondition(condition)) => {
parse_quote! {
#[cfg_attr(
#condition,
schemars(with = #attr_inner_tokens))
]
}
}
};
field.attrs.push(attr);
}
}
}
}
}
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_.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)]);
field.attrs.push(attr);
}
if let Some(type_) = serde_as_options.serialize_as {
let replacement_type = replace_infer_type_with_type(type_.clone(), type_same);
let attr_inner_tokens =
quote!(#serde_with_crate_path::As::<#replacement_type>::serialize).to_string();
let attr = parse_quote!(#[serde(serialize_with = #attr_inner_tokens)]);
field.attrs.push(attr);
}
Ok(())
}
fn replace_infer_type_with_type(to_replace: Type, replacement: &Type) -> Type {
match to_replace {
Type::Infer(_) => replacement.clone(),
Type::Array(mut inner) => {
*inner.elem = replace_infer_type_with_type(*inner.elem, replacement);
Type::Array(inner)
}
Type::Group(mut inner) => {
*inner.elem = replace_infer_type_with_type(*inner.elem, replacement);
Type::Group(inner)
}
Type::Never(inner) => Type::Never(inner),
Type::Paren(mut inner) => {
*inner.elem = replace_infer_type_with_type(*inner.elem, replacement);
Type::Paren(inner)
}
Type::Path(mut inner) => {
if let Some(Pair::End(mut t)) | Some(Pair::Punctuated(mut t, _)) =
inner.path.segments.pop()
{
t.arguments = match t.arguments {
PathArguments::None => PathArguments::None,
PathArguments::AngleBracketed(mut inner) => {
inner.args = inner
.args
.into_iter()
.map(|generic_argument| match generic_argument {
GenericArgument::Type(type_) => GenericArgument::Type(
replace_infer_type_with_type(type_, replacement),
),
ga => ga,
})
.collect();
PathArguments::AngleBracketed(inner)
}
PathArguments::Parenthesized(mut inner) => {
inner.inputs = inner
.inputs
.into_iter()
.map(|type_| replace_infer_type_with_type(type_, replacement))
.collect();
inner.output = match inner.output {
ReturnType::Type(arrow, mut type_) => {
*type_ = replace_infer_type_with_type(*type_, replacement);
ReturnType::Type(arrow, type_)
}
default => default,
};
PathArguments::Parenthesized(inner)
}
};
inner.path.segments.push(t);
}
Type::Path(inner)
}
Type::Ptr(mut inner) => {
*inner.elem = replace_infer_type_with_type(*inner.elem, replacement);
Type::Ptr(inner)
}
Type::Reference(mut inner) => {
*inner.elem = replace_infer_type_with_type(*inner.elem, replacement);
Type::Reference(inner)
}
Type::Slice(mut inner) => {
*inner.elem = replace_infer_type_with_type(*inner.elem, replacement);
Type::Slice(inner)
}
Type::Tuple(mut inner) => {
inner.elems = inner
.elems
.into_pairs()
.map(|pair| match pair {
Pair::Punctuated(type_, p) => {
Pair::Punctuated(replace_infer_type_with_type(type_, replacement), p)
}
Pair::End(type_) => Pair::End(replace_infer_type_with_type(type_, replacement)),
})
.collect();
Type::Tuple(inner)
}
type_ => type_,
}
}
fn has_type_embedded(type_: &Type, embedded_type: &syn::Ident) -> bool {
match type_ {
Type::Infer(_) => false,
Type::Never(_inner) => false,
Type::Array(inner) => has_type_embedded(&inner.elem, embedded_type),
Type::Group(inner) => has_type_embedded(&inner.elem, embedded_type),
Type::Paren(inner) => has_type_embedded(&inner.elem, embedded_type),
Type::Path(inner) => {
match inner.path.segments.last() {
Some(t) => {
if t.ident == *embedded_type {
return true;
}
match &t.arguments {
PathArguments::None => false,
PathArguments::AngleBracketed(inner) => {
inner
.args
.iter()
.any(|generic_argument| match generic_argument {
GenericArgument::Type(type_) => {
has_type_embedded(type_, embedded_type)
}
_ga => false,
})
}
PathArguments::Parenthesized(inner) => {
inner
.inputs
.iter()
.any(|type_| has_type_embedded(type_, embedded_type))
|| match &inner.output {
ReturnType::Type(_arrow, type_) => {
has_type_embedded(type_, embedded_type)
}
_default => false,
}
}
}
}
None => false,
}
}
Type::Ptr(inner) => has_type_embedded(&inner.elem, embedded_type),
Type::Reference(inner) => has_type_embedded(&inner.elem, embedded_type),
Type::Slice(inner) => has_type_embedded(&inner.elem, embedded_type),
Type::Tuple(inner) => inner.elems.pairs().any(|pair| match pair {
Pair::Punctuated(type_, _) | Pair::End(type_) => {
has_type_embedded(type_, embedded_type)
}
}),
_type_ => false,
}
}
#[proc_macro_derive(DeserializeFromStr, attributes(serde_with))]
pub fn derive_deserialize_fromstr(item: TokenStream) -> TokenStream {
let input: DeriveInput = parse_macro_input!(item);
let derive_options = match DeriveOptions::from_derive_input(&input) {
Ok(opt) => opt,
Err(err) => {
return err;
}
};
TokenStream::from(deserialize_fromstr(
input,
derive_options.get_serde_with_path(),
))
}
fn deserialize_fromstr(mut input: DeriveInput, serde_with_crate_path: Path) -> TokenStream2 {
let ident = input.ident;
let where_clause = &mut input.generics.make_where_clause().predicates;
where_clause.push(parse_quote!(Self: #serde_with_crate_path::__private__::FromStr));
where_clause.push(parse_quote!(
<Self as #serde_with_crate_path::__private__::FromStr>::Err: #serde_with_crate_path::__private__::Display
));
let (de_impl_generics, ty_generics, where_clause) = split_with_de_lifetime(&input.generics);
quote! {
#[automatically_derived]
impl #de_impl_generics #serde_with_crate_path::serde::Deserialize<'de> for #ident #ty_generics #where_clause {
fn deserialize<__D>(deserializer: __D) -> #serde_with_crate_path::__private__::Result<Self, __D::Error>
where
__D: #serde_with_crate_path::serde::Deserializer<'de>,
{
struct Helper<__S>(#serde_with_crate_path::__private__::PhantomData<__S>);
impl<'de, __S> #serde_with_crate_path::serde::de::Visitor<'de> for Helper<__S>
where
__S: #serde_with_crate_path::__private__::FromStr,
<__S as #serde_with_crate_path::__private__::FromStr>::Err: #serde_with_crate_path::__private__::Display,
{
type Value = __S;
fn expecting(&self, formatter: &mut #serde_with_crate_path::core::fmt::Formatter<'_>) -> #serde_with_crate_path::core::fmt::Result {
#serde_with_crate_path::__private__::Display::fmt("a string", formatter)
}
fn visit_str<__E>(
self,
value: &str
) -> #serde_with_crate_path::__private__::Result<Self::Value, __E>
where
__E: #serde_with_crate_path::serde::de::Error,
{
value.parse::<Self::Value>().map_err(#serde_with_crate_path::serde::de::Error::custom)
}
fn visit_bytes<__E>(
self,
value: &[u8]
) -> #serde_with_crate_path::__private__::Result<Self::Value, __E>
where
__E: #serde_with_crate_path::serde::de::Error,
{
let utf8 = #serde_with_crate_path::core::str::from_utf8(value).map_err(#serde_with_crate_path::serde::de::Error::custom)?;
self.visit_str(utf8)
}
}
deserializer.deserialize_str(Helper(#serde_with_crate_path::__private__::PhantomData))
}
}
}
}
#[proc_macro_derive(SerializeDisplay, attributes(serde_with))]
pub fn derive_serialize_display(item: TokenStream) -> TokenStream {
let input: DeriveInput = parse_macro_input!(item);
let derive_options = match DeriveOptions::from_derive_input(&input) {
Ok(opt) => opt,
Err(err) => {
return err;
}
};
TokenStream::from(serialize_display(
input,
false,
derive_options.get_serde_with_path(),
))
}
#[proc_macro_derive(SerializeDisplayAlt, attributes(serde_with))]
pub fn derive_serialize_display_alt(item: TokenStream) -> TokenStream {
let input: DeriveInput = parse_macro_input!(item);
let derive_options = match DeriveOptions::from_derive_input(&input) {
Ok(opt) => opt,
Err(err) => {
return err;
}
};
TokenStream::from(serialize_display(
input,
true,
derive_options.get_serde_with_path(),
))
}
fn serialize_display(
mut input: DeriveInput,
alternate: bool,
serde_with_crate_path: Path,
) -> TokenStream2 {
let ident = input.ident;
input
.generics
.make_where_clause()
.predicates
.push(parse_quote!(Self: #serde_with_crate_path::__private__::Display));
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let collect_str_param = if alternate {
quote! { &format_args!("{self:#}") }
} else {
quote! { &self }
};
quote! {
#[automatically_derived]
impl #impl_generics #serde_with_crate_path::serde::Serialize for #ident #ty_generics #where_clause {
fn serialize<__S>(
&self,
serializer: __S
) -> #serde_with_crate_path::__private__::Result<__S::Ok, __S::Error>
where
__S: #serde_with_crate_path::serde::Serializer,
{
serializer.collect_str(#collect_str_param)
}
}
}
}
#[doc(hidden)]
#[proc_macro_derive(
__private_consume_serde_as_attributes,
attributes(serde_as, serde_with)
)]
pub fn __private_consume_serde_as_attributes(_: TokenStream) -> TokenStream {
TokenStream::new()
}
#[proc_macro_attribute]
pub fn apply(args: TokenStream, input: TokenStream) -> TokenStream {
apply::apply(args, input)
}