zvariant_derive/
dict.rs

1use proc_macro2::{Span, TokenStream};
2use quote::{format_ident, quote};
3use syn::{punctuated::Punctuated, spanned::Spanned, Data, DeriveInput, Error, Field};
4use zvariant_utils::macros;
5
6use crate::utils::*;
7
8fn dict_name_for_field(
9    f: &Field,
10    rename_attr: Option<String>,
11    rename_all_attr: Option<&str>,
12) -> Result<String, Error> {
13    let ident = f.ident.as_ref().unwrap().to_string();
14    rename_identifier(ident, f.span(), rename_attr, rename_all_attr)
15}
16
17/// Implements `Serialize` for structs as D-Bus dictionaries via a serde helper.
18pub fn expand_serialize_derive(input: DeriveInput) -> Result<TokenStream, Error> {
19    let StructAttributes { rename_all, .. } = StructAttributes::parse(&input.attrs)?;
20    let rename_all_str = rename_all.as_deref().unwrap_or("snake_case");
21    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
22    let name = &input.ident;
23    let helper = format_ident!("__SerializeDict{}", name);
24    let zv = zvariant_path();
25
26    let mut field_defs = Vec::new();
27    let mut field_inits = Vec::new();
28    if let Data::Struct(data) = &input.data {
29        for field in &data.fields {
30            let ident = field.ident.as_ref().unwrap();
31            let ty = &field.ty;
32            let FieldAttributes { rename } = FieldAttributes::parse(&field.attrs)?;
33            let dict_name = dict_name_for_field(field, rename, rename_all.as_deref())?;
34            let is_opt = macros::ty_is_option(ty);
35            if is_opt {
36                let as_value_opt_path = quote! { #zv::as_value::optional };
37                let as_value_opt_str = format!("{as_value_opt_path}");
38                field_defs.push(quote! {
39                    #[serde(
40                        rename = #dict_name,
41                        with = #as_value_opt_str,
42                        skip_serializing_if = "Option::is_none",
43                    )]
44                    #ident: &'a #ty
45                });
46            } else {
47                let as_value_path = quote! { #zv::as_value };
48                let as_value_str = format!("{as_value_path}");
49                field_defs.push(quote! {
50                    #[serde(rename = #dict_name, with = #as_value_str)]
51                    #ident: &'a #ty
52                });
53            }
54            field_inits.push(quote! { #ident: &self.#ident });
55        }
56    } else {
57        return Err(Error::new(input.span(), "only structs supported"));
58    }
59
60    Ok(quote! {
61        #[allow(deprecated)]
62        impl #impl_generics #zv::export::serde::ser::Serialize for #name #ty_generics #where_clause {
63            fn serialize<S>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error>
64            where
65                S: #zv::export::serde::ser::Serializer,
66            {
67                use #zv::export::serde::Serialize;
68
69                #[derive(Serialize)]
70                #[serde(rename_all = #rename_all_str)]
71                struct #helper<'a> {
72                    #[serde(skip)]
73                    phantom: ::std::marker::PhantomData<&'a ()>,
74                    #(#field_defs,)*
75                }
76
77                let helper = #helper {
78                    phantom: ::std::marker::PhantomData,
79                    #(#field_inits,)*
80                };
81
82                helper.serialize(serializer)
83            }
84        }
85    })
86}
87
88/// Implements `Deserialize` for structs from D-Bus dictionaries via a serde helper.
89pub fn expand_deserialize_derive(input: DeriveInput) -> Result<TokenStream, Error> {
90    let StructAttributes {
91        rename_all,
92        deny_unknown_fields,
93        ..
94    } = StructAttributes::parse(&input.attrs)?;
95    let rename_all_str = rename_all.as_deref().unwrap_or("snake_case");
96    let zv = zvariant_path();
97
98    // Create a new generics with a 'de lifetime
99    let mut generics = input.generics.clone();
100    let lifetime_param = syn::LifetimeParam {
101        attrs: Vec::new(),
102        lifetime: syn::Lifetime::new("'de", Span::call_site()),
103        colon_token: None,
104        bounds: Punctuated::new(),
105    };
106    generics
107        .params
108        .insert(0, syn::GenericParam::Lifetime(lifetime_param));
109
110    let (impl_generics, _ty_generics, where_clause) = generics.split_for_impl();
111    let (_, orig_ty_generics, _) = input.generics.split_for_impl();
112    let name = &input.ident;
113    let helper = format_ident!("__DeserializeDict{}", name);
114
115    let mut field_defs = Vec::new();
116    let mut field_assignments = Vec::new();
117    let mut non_optional_field_checks = Vec::new();
118    if let Data::Struct(data) = &input.data {
119        for field in &data.fields {
120            let ident = field.ident.as_ref().unwrap();
121            let ty = &field.ty;
122            let FieldAttributes { rename } = FieldAttributes::parse(&field.attrs)?;
123            let dict_name = dict_name_for_field(field, rename, rename_all.as_deref())?;
124            let is_opt = macros::ty_is_option(ty);
125
126            if is_opt {
127                let as_value_opt_path = quote! { #zv::as_value::optional };
128                let as_value_opt_str = format!("{as_value_opt_path}");
129                field_defs.push(quote! {
130                    #[serde(rename = #dict_name, with = #as_value_opt_str, default)]
131                    #ident: #ty
132                });
133                field_assignments.push(quote! { #ident: helper.#ident });
134            } else {
135                // For non-optional fields, use Option<T> in helper for default support
136                let as_value_opt_path = quote! { #zv::as_value::optional };
137                let as_value_opt_str = format!("{as_value_opt_path}");
138                field_defs.push(quote! {
139                    #[serde(rename = #dict_name, with = #as_value_opt_str, default)]
140                    #ident: Option<#ty>
141                });
142
143                // Add a check to make sure this field was provided
144                non_optional_field_checks.push(quote! {
145                    if helper.#ident.is_none() {
146                        return Err(<D::Error as #zv::export::serde::de::Error>::missing_field(#dict_name));
147                    }
148                });
149
150                // Unwrap the option for field assignment
151                field_assignments.push(quote! { #ident: helper.#ident.unwrap() });
152            }
153        }
154    } else {
155        return Err(Error::new(input.span(), "only structs supported"));
156    }
157
158    let deny_attr = if deny_unknown_fields {
159        quote! { , deny_unknown_fields }
160    } else {
161        quote! {}
162    };
163
164    Ok(quote! {
165        #[allow(deprecated)]
166        impl #impl_generics #zv::export::serde::de::Deserialize<'de> for #name #orig_ty_generics
167        #where_clause
168        {
169            fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
170            where
171                D: #zv::export::serde::de::Deserializer<'de>,
172            {
173                use #zv::export::serde::Deserialize;
174
175                #[derive(Deserialize, Default)]
176                #[serde(default, rename_all = #rename_all_str #deny_attr)]
177                struct #helper {
178                    #(#field_defs,)*
179                }
180
181                let helper = #helper::deserialize(deserializer)?;
182
183                // Check for missing non-optional fields
184                #(#non_optional_field_checks)*
185
186                Ok(Self {
187                    #(#field_assignments,)*
188                })
189            }
190        }
191    })
192}