fp_bindgen/types/
structs.rs

1use super::TypeIdent;
2use crate::types::format_bounds;
3use crate::{casing::Casing, docs::get_doc_lines};
4use quote::ToTokens;
5use std::convert::TryFrom;
6use syn::{
7    ext::IdentExt, parenthesized, parse::Parse, parse::ParseStream, Attribute, Error, GenericParam,
8    Ident, ItemStruct, LitStr, Result, Token,
9};
10
11#[derive(Clone, Debug, Eq, Hash, PartialEq)]
12pub struct Struct {
13    pub ident: TypeIdent,
14    pub fields: Vec<Field>,
15    pub doc_lines: Vec<String>,
16    pub options: StructOptions,
17}
18
19pub(crate) fn parse_struct_item(item: ItemStruct) -> Struct {
20    let ident = TypeIdent {
21        name: item.ident.to_string(),
22        generic_args: item
23            .generics
24            .params
25            .iter()
26            .filter_map(|param| match param {
27                GenericParam::Type(ty) => {
28                    Some((TypeIdent::from(ty.ident.to_string()), format_bounds(ty)))
29                }
30                _ => None,
31            })
32            .collect(),
33        ..Default::default()
34    };
35    let fields = item
36        .fields
37        .iter()
38        .map(|field| Field {
39            name: field.ident.as_ref().map(Ident::to_string),
40            ty: TypeIdent::try_from(&field.ty)
41                .unwrap_or_else(|_| panic!("Invalid field type in struct {}", ident)),
42            doc_lines: get_doc_lines(&field.attrs),
43            attrs: FieldAttrs::from_attrs(&field.attrs),
44        })
45        .collect();
46
47    Struct {
48        ident,
49        fields,
50        doc_lines: get_doc_lines(&item.attrs),
51        options: StructOptions::from_attrs(&item.attrs),
52    }
53}
54
55#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
56pub struct StructOptions {
57    pub field_casing: Casing,
58
59    /// Rust module path where the type can be found for the given generator.
60    /// If present, the generator can use this type instead of generating it.
61    ///
62    /// ## Example:
63    ///
64    /// ```rs
65    /// #[fp(rust_module = "my_crate")]
66    /// struct MyStruct { /* ... */ }
67    /// ```
68    ///
69    /// This will set `"my_crate"` as the rust_module, to
70    /// be used by the Rust plugin generator to generate a `use` statement such
71    /// as:
72    ///
73    /// ```rs
74    /// pub use my_crate::MyStruct;
75    /// ```
76    ///
77    /// Instead of generating the struct definition itself.
78    pub rust_module: Option<String>,
79}
80
81impl StructOptions {
82    pub fn from_attrs(attrs: &[Attribute]) -> Self {
83        let mut opts = Self::default();
84        for attr in attrs {
85            if attr.path.is_ident("fp") || attr.path.is_ident("serde") {
86                opts.merge_with(
87                    &syn::parse2::<Self>(attr.tokens.clone()).expect("Could not parse attributes"),
88                );
89            }
90        }
91        opts
92    }
93
94    fn merge_with(&mut self, other: &Self) {
95        if other.field_casing != Casing::default() {
96            self.field_casing = other.field_casing;
97        }
98        if let Some(other_rust_module) = &other.rust_module {
99            self.rust_module = Some(other_rust_module.clone());
100        }
101    }
102
103    pub fn to_serde_attrs(&self) -> Vec<String> {
104        let mut serde_attrs = vec![];
105        if let Some(casing) = &self.field_casing.as_maybe_str() {
106            serde_attrs.push(format!("rename_all = \"{casing}\""));
107        }
108        serde_attrs
109    }
110}
111
112impl Parse for StructOptions {
113    fn parse(input: ParseStream) -> Result<Self> {
114        let content;
115        parenthesized!(content in input);
116
117        let parse_value = || -> Result<String> {
118            content.parse::<Token![=]>()?;
119            Ok(content
120                .parse::<LitStr>()?
121                .to_token_stream()
122                .to_string()
123                .trim_matches('"')
124                .to_owned())
125        };
126
127        let mut result = Self::default();
128        loop {
129            let key: Ident = content.call(IdentExt::parse_any)?;
130            match key.to_string().as_ref() {
131                "rename_all" => {
132                    result.field_casing = Casing::try_from(parse_value()?.as_ref())
133                        .map_err(|err| Error::new(content.span(), err))?
134                }
135                "rust_module" => {
136                    result.rust_module = Some(parse_value()?);
137                }
138                other => {
139                    return Err(Error::new(
140                        content.span(),
141                        format!("Unexpected attribute: {other}"),
142                    ))
143                }
144            }
145
146            if content.is_empty() {
147                break;
148            }
149
150            content.parse::<Token![,]>()?;
151        }
152
153        Ok(result)
154    }
155}
156
157#[derive(Clone, Debug, Eq, Hash, PartialEq)]
158pub struct Field {
159    pub name: Option<String>,
160    pub ty: TypeIdent,
161    pub doc_lines: Vec<String>,
162    pub attrs: FieldAttrs,
163}
164
165#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
166pub struct FieldAttrs {
167    /// Optional path to a function that will produce the default value in case
168    /// the field is omitted from the serialized representation.
169    ///
170    /// See also: <https://serde.rs/field-attrs.html#default--path>
171    ///
172    /// An empty string may be used as value, in which case `Default::default`
173    /// is assumed. See also: <https://serde.rs/field-attrs.html#default>
174    pub default: Option<String>,
175
176    /// Optional Serde dependency used for deserialization.
177    ///
178    /// See: <https://serde.rs/field-attrs.html#deserialize_with>
179    pub deserialize_with: Option<String>,
180
181    /// Determines whether the field should be flattened into the parent struct.
182    ///
183    /// See: <https://serde.rs/attr-flatten.html>
184    pub flatten: bool,
185
186    /// Optional name to use in the serialized format
187    /// (only used if different than the field name itself).
188    ///
189    /// See also: <https://serde.rs/field-attrs.html#rename>
190    pub rename: Option<String>,
191
192    /// Optional Serde dependency used for serialization.
193    ///
194    /// See also: <https://serde.rs/field-attrs.html#serialize_with>
195    pub serialize_with: Option<String>,
196
197    /// Optional path to a function to determine whether serialized should be
198    /// skipped for a particular value.
199    ///
200    /// E.g.: can be used to omit `Option`s by specifying
201    /// `skip_serializing_if = "Option::is_none"`.
202    ///
203    /// See also: <https://serde.rs/field-attrs.html#skip_serializing_if>
204    pub skip_serializing_if: Option<String>,
205}
206
207impl FieldAttrs {
208    pub fn from_attrs(attrs: &[Attribute]) -> Self {
209        let mut opts = Self::default();
210        for attr in attrs {
211            if attr.path.is_ident("fp") || attr.path.is_ident("serde") {
212                opts.merge_with(
213                    &syn::parse2::<Self>(attr.tokens.clone())
214                        .expect("Could not parse field attributes"),
215                );
216            }
217        }
218        opts
219    }
220
221    fn merge_with(&mut self, other: &Self) {
222        if other.default.is_some() {
223            self.default = other.default.clone();
224        }
225        if other.deserialize_with.is_some() {
226            self.deserialize_with = other.deserialize_with.clone();
227        }
228        if other.flatten {
229            self.flatten = other.flatten;
230        }
231        if other.rename.is_some() {
232            self.rename = other.rename.clone();
233        }
234        if other.serialize_with.is_some() {
235            self.serialize_with = other.serialize_with.clone();
236        }
237        if other.skip_serializing_if.is_some() {
238            self.skip_serializing_if = other.skip_serializing_if.clone();
239        }
240    }
241
242    pub fn to_serde_attrs(&self) -> Vec<String> {
243        let mut serde_attrs = vec![];
244        if let Some(default) = self.default.as_ref() {
245            if default.is_empty() {
246                serde_attrs.push("default".to_owned());
247            } else {
248                serde_attrs.push(format!("default = \"{default}\""));
249            }
250        }
251        match (self.deserialize_with.as_ref(), self.serialize_with.as_ref()) {
252            (Some(deserialize_with), Some(serialize_with))
253                if deserialize_with == serialize_with =>
254            {
255                serde_attrs.push(format!("with = \"{deserialize_with}\""));
256            }
257            (Some(deserialize_with), Some(serialize_with)) => {
258                serde_attrs.push(format!("deserialize_with = \"{deserialize_with}\""));
259                serde_attrs.push(format!("serialize_with = \"{serialize_with}\""));
260            }
261            (Some(deserialize_with), None) => {
262                serde_attrs.push(format!("deserialize_with = \"{deserialize_with}\""));
263            }
264            (None, Some(serialize_with)) => {
265                serde_attrs.push(format!("serialize_with = \"{serialize_with}\""));
266            }
267            (None, None) => {}
268        }
269        if self.flatten {
270            serde_attrs.push("flatten".to_owned());
271        }
272        if let Some(rename) = self.rename.as_ref() {
273            serde_attrs.push(format!("rename = \"{rename}\""));
274        }
275        if let Some(skip_serializing_if) = self.skip_serializing_if.as_ref() {
276            serde_attrs.push(format!("skip_serializing_if = \"{skip_serializing_if}\""));
277        }
278        serde_attrs
279    }
280}
281
282impl Parse for FieldAttrs {
283    fn parse(input: ParseStream) -> Result<Self> {
284        let content;
285        parenthesized!(content in input);
286
287        let parse_value = || -> Result<String> {
288            content.parse::<Token![=]>()?;
289            Ok(content
290                .parse::<LitStr>()?
291                .to_token_stream()
292                .to_string()
293                .trim_matches('"')
294                .to_owned())
295        };
296
297        let parse_optional_value = || -> Result<String> {
298            if content.peek(Token![=]) {
299                parse_value()
300            } else {
301                Ok(String::new())
302            }
303        };
304
305        let mut result = Self::default();
306        loop {
307            let key: Ident = content.call(IdentExt::parse_any)?;
308            match key.to_string().as_ref() {
309                "default" => result.default = Some(parse_optional_value()?),
310                "deserialize_with" => result.deserialize_with = Some(parse_value()?),
311                "flatten" => result.flatten = true,
312                "rename" => result.rename = Some(parse_value()?),
313                "serialize_with" => result.serialize_with = Some(parse_value()?),
314                "skip_serializing_if" => result.skip_serializing_if = Some(parse_value()?),
315                "with" => {
316                    let value = parse_value()?;
317                    result.deserialize_with = Some(value.clone());
318                    result.serialize_with = Some(value);
319                }
320                other => {
321                    return Err(Error::new(
322                        content.span(),
323                        format!("Unexpected field attribute: {other}"),
324                    ))
325                }
326            }
327
328            if content.is_empty() {
329                break;
330            }
331
332            content.parse::<Token![,]>()?;
333        }
334
335        Ok(result)
336    }
337}