Skip to main content

zvariant_utils/derive/
dict.rs

1use std::str::FromStr;
2
3use proc_macro2::{Span, TokenStream};
4use quote::{format_ident, quote};
5use syn::{Data, DeriveInput, Error, Field, punctuated::Punctuated, spanned::Spanned};
6
7use super::{Config, attrs::*};
8use crate::{macros, signature::Signature};
9
10fn dict_name_for_field(
11    f: &Field,
12    rename_attr: Option<String>,
13    rename_all_attr: Option<&str>,
14) -> Result<String, Error> {
15    let ident = f.ident.as_ref().unwrap().to_string();
16    rename_identifier(ident, f.span(), rename_attr, rename_all_attr)
17}
18
19/// Whether the dict's value type is `Variant` (i.e. signature `a{?v}`).
20///
21/// Variant-typed values get wrapped/unwrapped via `as_value`; any other value type defers to
22/// the field type's own `Serialize`/`Deserialize`.
23fn dict_value_is_variant(signature: Option<&str>, span: Span) -> Result<bool, Error> {
24    let Some(s) = signature else {
25        return Ok(true);
26    };
27    if s == "dict" {
28        return Ok(true);
29    }
30    let sig = Signature::from_str(s).map_err(|e| Error::new(span, e))?;
31    match sig {
32        Signature::Dict { value, .. } => match &*value {
33            Signature::Variant => Ok(true),
34            _ => Ok(false),
35        },
36        _ => Err(Error::new(
37            span,
38            "`*Dict` derive requires a dictionary signature (e.g. `a{sv}` or `a{sa{sv}}`)",
39        )),
40    }
41}
42
43/// Implements `Serialize` for structs as D-Bus dictionaries via a serde helper.
44pub fn expand_serialize_dict_derive(
45    input: DeriveInput,
46    config: &Config,
47) -> Result<TokenStream, Error> {
48    let StructAttributes {
49        signature,
50        rename_all,
51        crate_path: crate_attr,
52        ..
53    } = StructAttributes::parse_with_lists(&input.attrs, config.attr_lists)?;
54    let value_is_variant = dict_value_is_variant(signature.as_deref(), input.span())?;
55    let rename_all_str = rename_all.as_deref().unwrap_or("snake_case");
56    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
57    let name = &input.ident;
58    let helper = format_ident!("__SerializeDict{}", name);
59    let zv = config.resolve_path(crate_attr.as_deref())?;
60
61    let mut field_defs = Vec::new();
62    let mut field_inits = Vec::new();
63    let Data::Struct(data) = &input.data else {
64        return Err(Error::new(input.span(), "only structs supported"));
65    };
66    for field in &data.fields {
67        let ident = field.ident.as_ref().unwrap();
68        let ty = &field.ty;
69        let FieldAttributes { rename, .. } =
70            FieldAttributes::parse_with_lists(&field.attrs, config.attr_lists)?;
71        let dict_name = dict_name_for_field(field, rename, rename_all.as_deref())?;
72        let is_opt = macros::ty_is_option(ty);
73        let field_def = match (value_is_variant, is_opt) {
74            (true, true) => {
75                let path = format!("{}::as_value::optional", quote! { #zv });
76                quote! {
77                    #[serde(
78                        rename = #dict_name,
79                        with = #path,
80                        skip_serializing_if = "Option::is_none",
81                    )]
82                    #ident: &'a #ty
83                }
84            }
85            (true, false) => {
86                let path = format!("{}::as_value", quote! { #zv });
87                quote! {
88                    #[serde(rename = #dict_name, with = #path)]
89                    #ident: &'a #ty
90                }
91            }
92            (false, true) => quote! {
93                #[serde(
94                    rename = #dict_name,
95                    serialize_with = "__zv_dict_ser_opt",
96                    skip_serializing_if = "Option::is_none",
97                )]
98                #ident: &'a #ty
99            },
100            (false, false) => quote! {
101                #[serde(rename = #dict_name)]
102                #ident: &'a #ty
103            },
104        };
105        field_defs.push(field_def);
106        field_inits.push(quote! { #ident: &self.#ident });
107    }
108
109    let opt_serializer = (!value_is_variant).then(|| {
110        quote! {
111            fn __zv_dict_ser_opt<T, S>(
112                value: &::std::option::Option<T>,
113                serializer: S,
114            ) -> ::std::result::Result<S::Ok, S::Error>
115            where
116                T: #zv::export::serde::Serialize,
117                S: #zv::export::serde::Serializer,
118            {
119                <T as #zv::export::serde::Serialize>::serialize(
120                    value.as_ref().unwrap(),
121                    serializer,
122                )
123            }
124        }
125    });
126
127    Ok(quote! {
128        #[allow(deprecated)]
129        impl #impl_generics #zv::export::serde::ser::Serialize for #name #ty_generics #where_clause {
130            fn serialize<S>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error>
131            where
132                S: #zv::export::serde::ser::Serializer,
133            {
134                use #zv::export::serde::Serialize;
135
136                #opt_serializer
137
138                #[derive(Serialize)]
139                #[serde(rename_all = #rename_all_str)]
140                struct #helper<'a> {
141                    #[serde(skip)]
142                    phantom: ::std::marker::PhantomData<&'a ()>,
143                    #(#field_defs,)*
144                }
145
146                let helper = #helper {
147                    phantom: ::std::marker::PhantomData,
148                    #(#field_inits,)*
149                };
150
151                helper.serialize(serializer)
152            }
153        }
154    })
155}
156
157/// Implements `Deserialize` for structs from D-Bus dictionaries via a serde helper.
158pub fn expand_deserialize_dict_derive(
159    input: DeriveInput,
160    config: &Config,
161) -> Result<TokenStream, Error> {
162    let StructAttributes {
163        signature,
164        rename_all,
165        deny_unknown_fields,
166        crate_path: crate_attr,
167        ..
168    } = StructAttributes::parse_with_lists(&input.attrs, config.attr_lists)?;
169    let value_is_variant = dict_value_is_variant(signature.as_deref(), input.span())?;
170    let rename_all_str = rename_all.as_deref().unwrap_or("snake_case");
171    let zv = config.resolve_path(crate_attr.as_deref())?;
172
173    // Create a new generics with a 'de lifetime
174    let mut generics = input.generics.clone();
175    let lifetime_param = syn::LifetimeParam {
176        attrs: Vec::new(),
177        lifetime: syn::Lifetime::new("'de", Span::call_site()),
178        colon_token: None,
179        bounds: Punctuated::new(),
180    };
181    generics
182        .params
183        .insert(0, syn::GenericParam::Lifetime(lifetime_param));
184
185    let (impl_generics, _ty_generics, where_clause) = generics.split_for_impl();
186    let (_, orig_ty_generics, _) = input.generics.split_for_impl();
187    let name = &input.ident;
188    let helper = format_ident!("__DeserializeDict{}", name);
189
190    let mut field_defs = Vec::new();
191    let mut field_assignments = Vec::new();
192    let mut non_optional_field_checks = Vec::new();
193    let Data::Struct(data) = &input.data else {
194        return Err(Error::new(input.span(), "only structs supported"));
195    };
196    let opt_path = if value_is_variant {
197        format!("{}::as_value::optional", quote! { #zv })
198    } else {
199        "__zv_dict_de_opt".to_string()
200    };
201    for field in &data.fields {
202        let ident = field.ident.as_ref().unwrap();
203        let ty = &field.ty;
204        let FieldAttributes { rename, .. } =
205            FieldAttributes::parse_with_lists(&field.attrs, config.attr_lists)?;
206        let dict_name = dict_name_for_field(field, rename, rename_all.as_deref())?;
207        let is_opt = macros::ty_is_option(ty);
208
209        let with_attr = if value_is_variant {
210            quote! { with = #opt_path }
211        } else {
212            quote! { deserialize_with = #opt_path }
213        };
214
215        if is_opt {
216            field_defs.push(quote! {
217                #[serde(rename = #dict_name, #with_attr, default)]
218                #ident: #ty
219            });
220            field_assignments.push(quote! { #ident: helper.#ident });
221        } else {
222            field_defs.push(quote! {
223                #[serde(rename = #dict_name, #with_attr, default)]
224                #ident: ::std::option::Option<#ty>
225            });
226
227            non_optional_field_checks.push(quote! {
228                if helper.#ident.is_none() {
229                    return ::std::result::Result::Err(
230                        <D::Error as #zv::export::serde::de::Error>::missing_field(#dict_name),
231                    );
232                }
233            });
234
235            field_assignments.push(quote! { #ident: helper.#ident.unwrap() });
236        }
237    }
238
239    let deny_attr = if deny_unknown_fields {
240        quote! { , deny_unknown_fields }
241    } else {
242        quote! {}
243    };
244
245    let opt_deserializer = (!value_is_variant).then(|| {
246        quote! {
247            fn __zv_dict_de_opt<'de, T, D>(
248                deserializer: D,
249            ) -> ::std::result::Result<::std::option::Option<T>, D::Error>
250            where
251                T: #zv::export::serde::Deserialize<'de>,
252                D: #zv::export::serde::Deserializer<'de>,
253            {
254                <T as #zv::export::serde::Deserialize<'de>>::deserialize(deserializer)
255                    .map(::std::option::Option::Some)
256            }
257        }
258    });
259
260    Ok(quote! {
261        #[allow(deprecated)]
262        impl #impl_generics #zv::export::serde::de::Deserialize<'de> for #name #orig_ty_generics
263        #where_clause
264        {
265            fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
266            where
267                D: #zv::export::serde::de::Deserializer<'de>,
268            {
269                use #zv::export::serde::Deserialize;
270
271                #opt_deserializer
272
273                #[derive(Deserialize, Default)]
274                #[serde(default, rename_all = #rename_all_str #deny_attr)]
275                struct #helper {
276                    #(#field_defs,)*
277                }
278
279                let helper = #helper::deserialize(deserializer)?;
280
281                #(#non_optional_field_checks)*
282
283                Ok(Self {
284                    #(#field_assignments,)*
285                })
286            }
287        }
288    })
289}