Skip to main content

tauri_typegen/analysis/
serde_parser.rs

1use serde_rename_rule::RenameRule;
2use syn::{Attribute, Expr, ExprLit, Lit};
3
4/// Parser for serde attributes from Rust struct/enum definitions and fields
5#[derive(Debug)]
6pub struct SerdeParser;
7
8impl SerdeParser {
9    pub fn new() -> Self {
10        Self
11    }
12
13    /// Parse struct-level serde attributes (e.g., rename_all, tag, content)
14    pub fn parse_struct_serde_attrs(&self, attrs: &[Attribute]) -> SerdeStructAttributes {
15        let mut result = SerdeStructAttributes {
16            rename_all: None,
17            tag: None,
18            content: None,
19        };
20
21        for attr in attrs {
22            if attr.path().is_ident("serde") {
23                let _ = attr.parse_nested_meta(|meta| {
24                    if meta.path.is_ident("rename_all") {
25                        if let Some(value) = parse_string_value(&meta)? {
26                            result.rename_all = RenameRule::from_rename_all_str(&value).ok();
27                        }
28                    } else if meta.path.is_ident("tag") {
29                        result.tag = parse_string_value(&meta)?;
30                    } else if meta.path.is_ident("content") {
31                        result.content = parse_string_value(&meta)?;
32                    }
33                    Ok(())
34                });
35            }
36        }
37
38        result
39    }
40
41    /// Parse field-level serde attributes (e.g., rename, skip)
42    pub fn parse_field_serde_attrs(&self, attrs: &[Attribute]) -> SerdeFieldAttributes {
43        let mut result = SerdeFieldAttributes {
44            rename: None,
45            skip: false,
46        };
47
48        for attr in attrs {
49            if attr.path().is_ident("serde") {
50                let _ = attr.parse_nested_meta(|meta| {
51                    if meta.path.is_ident("rename") {
52                        result.rename = parse_string_value(&meta)?;
53                    } else if meta.path.is_ident("skip") {
54                        // skip is a flag, no value needed
55                        result.skip = true;
56                    }
57                    // Note: skip_serializing and skip_deserializing are NOT the same as skip
58                    // They only affect one direction, so we don't set the skip flag for them
59                    Ok(())
60                });
61            }
62        }
63
64        result
65    }
66}
67
68/// Parse a string value from a meta item like `name = "value"`
69fn parse_string_value(meta: &syn::meta::ParseNestedMeta) -> syn::Result<Option<String>> {
70    let expr: Expr = meta.value()?.parse()?;
71    if let Expr::Lit(ExprLit {
72        lit: Lit::Str(lit_str),
73        ..
74    }) = expr
75    {
76        Ok(Some(lit_str.value()))
77    } else {
78        Ok(None)
79    }
80}
81
82impl Default for SerdeParser {
83    fn default() -> Self {
84        Self::new()
85    }
86}
87
88/// Struct-level serde attributes
89#[derive(Debug, Default, Clone)]
90pub struct SerdeStructAttributes {
91    pub rename_all: Option<RenameRule>,
92    /// Tag attribute for internally-tagged enum representation: #[serde(tag = "type")]
93    pub tag: Option<String>,
94    /// Content attribute for adjacently-tagged enum representation: #[serde(content = "data")]
95    pub content: Option<String>,
96}
97
98/// Field-level serde attributes
99#[derive(Debug, Default, Clone)]
100pub struct SerdeFieldAttributes {
101    pub rename: Option<String>,
102    pub skip: bool,
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108    use syn::parse_quote;
109
110    #[test]
111    fn test_parse_struct_serde_attrs_with_rename_all_camel_case() {
112        let parser = SerdeParser::new();
113        let attrs: Vec<Attribute> = vec![parse_quote!(#[serde(rename_all = "camelCase")])];
114
115        let result = parser.parse_struct_serde_attrs(&attrs);
116        assert!(result.rename_all.is_some());
117        assert!(matches!(result.rename_all.unwrap(), RenameRule::CamelCase));
118    }
119
120    #[test]
121    fn test_parse_struct_serde_attrs_with_rename_all_snake_case() {
122        let parser = SerdeParser::new();
123        let attrs: Vec<Attribute> = vec![parse_quote!(#[serde(rename_all = "snake_case")])];
124
125        let result = parser.parse_struct_serde_attrs(&attrs);
126        assert!(result.rename_all.is_some());
127        assert!(matches!(result.rename_all.unwrap(), RenameRule::SnakeCase));
128    }
129
130    #[test]
131    fn test_parse_struct_serde_attrs_with_rename_all_pascal_case() {
132        let parser = SerdeParser::new();
133        let attrs: Vec<Attribute> = vec![parse_quote!(#[serde(rename_all = "PascalCase")])];
134
135        let result = parser.parse_struct_serde_attrs(&attrs);
136        assert!(result.rename_all.is_some());
137        assert!(matches!(result.rename_all.unwrap(), RenameRule::PascalCase));
138    }
139
140    #[test]
141    fn test_parse_struct_serde_attrs_with_rename_all_screaming_snake_case() {
142        let parser = SerdeParser::new();
143        let attrs: Vec<Attribute> =
144            vec![parse_quote!(#[serde(rename_all = "SCREAMING_SNAKE_CASE")])];
145
146        let result = parser.parse_struct_serde_attrs(&attrs);
147        assert!(result.rename_all.is_some());
148        assert!(matches!(
149            result.rename_all.unwrap(),
150            RenameRule::ScreamingSnakeCase
151        ));
152    }
153
154    #[test]
155    fn test_parse_struct_serde_attrs_with_rename_all_kebab_case() {
156        let parser = SerdeParser::new();
157        let attrs: Vec<Attribute> = vec![parse_quote!(#[serde(rename_all = "kebab-case")])];
158
159        let result = parser.parse_struct_serde_attrs(&attrs);
160        assert!(result.rename_all.is_some());
161        assert!(matches!(result.rename_all.unwrap(), RenameRule::KebabCase));
162    }
163
164    #[test]
165    fn test_parse_struct_serde_attrs_no_serde() {
166        let parser = SerdeParser::new();
167        let attrs: Vec<Attribute> = vec![parse_quote!(#[derive(Debug)])];
168
169        let result = parser.parse_struct_serde_attrs(&attrs);
170        assert!(result.rename_all.is_none());
171        assert!(result.tag.is_none());
172        assert!(result.content.is_none());
173    }
174
175    #[test]
176    fn test_parse_struct_serde_attrs_with_tag() {
177        let parser = SerdeParser::new();
178        let attrs: Vec<Attribute> = vec![parse_quote!(#[serde(tag = "type")])];
179
180        let result = parser.parse_struct_serde_attrs(&attrs);
181        assert_eq!(result.tag, Some("type".to_string()));
182    }
183
184    #[test]
185    fn test_parse_struct_serde_attrs_with_custom_tag() {
186        let parser = SerdeParser::new();
187        let attrs: Vec<Attribute> = vec![parse_quote!(#[serde(tag = "kind")])];
188
189        let result = parser.parse_struct_serde_attrs(&attrs);
190        assert_eq!(result.tag, Some("kind".to_string()));
191    }
192
193    #[test]
194    fn test_parse_struct_serde_attrs_with_content() {
195        let parser = SerdeParser::new();
196        let attrs: Vec<Attribute> = vec![parse_quote!(#[serde(content = "data")])];
197
198        let result = parser.parse_struct_serde_attrs(&attrs);
199        assert_eq!(result.content, Some("data".to_string()));
200    }
201
202    #[test]
203    fn test_parse_struct_serde_attrs_with_tag_and_content() {
204        let parser = SerdeParser::new();
205        let attrs: Vec<Attribute> = vec![parse_quote!(#[serde(tag = "kind", content = "data")])];
206
207        let result = parser.parse_struct_serde_attrs(&attrs);
208        assert_eq!(result.tag, Some("kind".to_string()));
209        assert_eq!(result.content, Some("data".to_string()));
210    }
211
212    #[test]
213    fn test_parse_struct_serde_attrs_with_all_attributes() {
214        let parser = SerdeParser::new();
215        let attrs: Vec<Attribute> =
216            vec![parse_quote!(#[serde(rename_all = "camelCase", tag = "type", content = "value")])];
217
218        let result = parser.parse_struct_serde_attrs(&attrs);
219        assert!(matches!(result.rename_all, Some(RenameRule::CamelCase)));
220        assert_eq!(result.tag, Some("type".to_string()));
221        assert_eq!(result.content, Some("value".to_string()));
222    }
223
224    #[test]
225    fn test_parse_field_serde_attrs_with_rename() {
226        let parser = SerdeParser::new();
227        let attrs: Vec<Attribute> = vec![parse_quote!(#[serde(rename = "customName")])];
228
229        let result = parser.parse_field_serde_attrs(&attrs);
230        assert_eq!(result.rename, Some("customName".to_string()));
231        assert!(!result.skip);
232    }
233
234    #[test]
235    fn test_parse_field_serde_attrs_with_skip() {
236        let parser = SerdeParser::new();
237        let attrs: Vec<Attribute> = vec![parse_quote!(#[serde(skip)])];
238
239        let result = parser.parse_field_serde_attrs(&attrs);
240        assert!(result.skip);
241        assert!(result.rename.is_none());
242    }
243
244    #[test]
245    fn test_parse_field_serde_attrs_skip_serializing_not_skip() {
246        let parser = SerdeParser::new();
247        let attrs: Vec<Attribute> = vec![parse_quote!(#[serde(skip_serializing)])];
248
249        let result = parser.parse_field_serde_attrs(&attrs);
250        // skip_serializing should not set skip flag
251        assert!(!result.skip);
252    }
253
254    #[test]
255    fn test_parse_field_serde_attrs_skip_deserializing_not_skip() {
256        let parser = SerdeParser::new();
257        let attrs: Vec<Attribute> = vec![parse_quote!(#[serde(skip_deserializing)])];
258
259        let result = parser.parse_field_serde_attrs(&attrs);
260        // skip_deserializing should not set skip flag
261        assert!(!result.skip);
262    }
263
264    #[test]
265    fn test_parse_field_serde_attrs_multiple_attributes() {
266        let parser = SerdeParser::new();
267        let attrs: Vec<Attribute> = vec![
268            parse_quote!(#[serde(rename = "id")]),
269            parse_quote!(#[derive(Debug)]),
270        ];
271
272        let result = parser.parse_field_serde_attrs(&attrs);
273        assert_eq!(result.rename, Some("id".to_string()));
274    }
275
276    #[test]
277    fn test_parse_field_serde_attrs_rename_and_skip() {
278        let parser = SerdeParser::new();
279        let attrs: Vec<Attribute> = vec![parse_quote!(#[serde(rename = "id", skip)])];
280
281        let result = parser.parse_field_serde_attrs(&attrs);
282        assert_eq!(result.rename, Some("id".to_string()));
283        assert!(result.skip);
284    }
285
286    #[test]
287    fn test_parse_field_serde_attrs_no_serde() {
288        let parser = SerdeParser::new();
289        let attrs: Vec<Attribute> = vec![parse_quote!(#[derive(Debug)])];
290
291        let result = parser.parse_field_serde_attrs(&attrs);
292        assert!(result.rename.is_none());
293        assert!(!result.skip);
294    }
295
296    #[test]
297    fn test_parse_field_serde_attrs_empty() {
298        let parser = SerdeParser::new();
299        let attrs: Vec<Attribute> = vec![];
300
301        let result = parser.parse_field_serde_attrs(&attrs);
302        assert!(result.rename.is_none());
303        assert!(!result.skip);
304    }
305
306    #[test]
307    fn test_default_impl() {
308        let parser = SerdeParser::new();
309        let attrs: Vec<Attribute> = vec![parse_quote!(#[serde(rename = "test")])];
310        let result = parser.parse_field_serde_attrs(&attrs);
311        assert_eq!(result.rename, Some("test".to_string()));
312    }
313
314    #[test]
315    fn test_parse_struct_serde_attrs_ignores_other_attributes() {
316        let parser = SerdeParser::new();
317        let attrs: Vec<Attribute> = vec![parse_quote!(
318            #[serde(rename_all = "camelCase", deny_unknown_fields)]
319        )];
320
321        let result = parser.parse_struct_serde_attrs(&attrs);
322        assert!(matches!(result.rename_all, Some(RenameRule::CamelCase)));
323        // deny_unknown_fields is ignored, no error
324    }
325
326    #[test]
327    fn test_parse_field_serde_attrs_ignores_other_attributes() {
328        let parser = SerdeParser::new();
329        let attrs: Vec<Attribute> = vec![
330            parse_quote!(#[serde(rename = "id", default, skip_serializing_if = "Option::is_none")]),
331        ];
332
333        let result = parser.parse_field_serde_attrs(&attrs);
334        assert_eq!(result.rename, Some("id".to_string()));
335        // default and skip_serializing_if are ignored, no error
336    }
337
338    #[test]
339    fn test_parse_multiple_serde_attributes() {
340        let parser = SerdeParser::new();
341        let attrs: Vec<Attribute> = vec![
342            parse_quote!(#[serde(rename_all = "camelCase")]),
343            parse_quote!(#[serde(tag = "type")]),
344        ];
345
346        let result = parser.parse_struct_serde_attrs(&attrs);
347        assert!(matches!(result.rename_all, Some(RenameRule::CamelCase)));
348        assert_eq!(result.tag, Some("type".to_string()));
349    }
350}