Skip to main content

serde_attr/
lib.rs

1//! Parse `#[serde(...)]` attributes for derive macros.
2//!
3//! Used by derive macros (ts-rs, flowjs-rs, etc.) that need to read serde attributes
4//! to produce type definitions matching serde's serialization behavior.
5//!
6//! Handles the "always consume the value" pattern to avoid parse buffer issues
7//! when serde attributes coexist with other derive attributes.
8
9use derive_inflection::Inflection;
10use syn::{Attribute, Lit, Result};
11
12/// Serde tagging model for enums.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum Tagged {
15    /// Default: `{ "VariantName": data }`
16    Externally,
17    /// `#[serde(tag = "type")]`: `{ "type": "VariantName", ...data }`
18    Internally { tag: String },
19    /// `#[serde(tag = "t", content = "c")]`: `{ "t": "VariantName", "c": data }`
20    Adjacently { tag: String, content: String },
21    /// `#[serde(untagged)]`: just `data`
22    Untagged,
23}
24
25/// Serde attributes parsed from a container (struct or enum).
26#[derive(Debug, Clone, Default)]
27pub struct SerdeContainer {
28    pub rename: Option<String>,
29    pub rename_all: Option<Inflection>,
30    pub rename_all_fields: Option<Inflection>,
31    pub tag: Option<String>,
32    pub content: Option<String>,
33    pub untagged: bool,
34    pub transparent: bool,
35}
36
37impl SerdeContainer {
38    /// Parse serde container attributes from a list of `syn::Attribute`.
39    pub fn from_attrs(attrs: &[Attribute]) -> Result<Self> {
40        let mut this = Self::default();
41
42        for attr in attrs {
43            if !attr.path().is_ident("serde") {
44                continue;
45            }
46            attr.parse_nested_meta(|meta| {
47                // Always consume values to avoid leaving the parse buffer dirty.
48                if meta.path.is_ident("rename") {
49                    let value: Lit = meta.value()?.parse()?;
50                    if let Lit::Str(s) = value {
51                        this.rename = Some(s.value());
52                    }
53                } else if meta.path.is_ident("rename_all") {
54                    let value: Lit = meta.value()?.parse()?;
55                    if let Lit::Str(s) = value {
56                        this.rename_all = Inflection::parse(&s.value());
57                    }
58                } else if meta.path.is_ident("rename_all_fields") {
59                    let value: Lit = meta.value()?.parse()?;
60                    if let Lit::Str(s) = value {
61                        this.rename_all_fields = Inflection::parse(&s.value());
62                    }
63                } else if meta.path.is_ident("tag") {
64                    let value: Lit = meta.value()?.parse()?;
65                    if let Lit::Str(s) = value {
66                        this.tag = Some(s.value());
67                    }
68                } else if meta.path.is_ident("content") {
69                    let value: Lit = meta.value()?.parse()?;
70                    if let Lit::Str(s) = value {
71                        this.content = Some(s.value());
72                    }
73                } else if meta.path.is_ident("untagged") {
74                    this.untagged = true;
75                } else if meta.path.is_ident("transparent") {
76                    this.transparent = true;
77                } else {
78                    // Consume unknown attributes to keep the parse buffer clean
79                    let _ = meta.value().and_then(|v| v.parse::<Lit>()).ok();
80                }
81                Ok(())
82            })?;
83        }
84
85        Ok(this)
86    }
87
88    /// Resolve the tagging model from the parsed attributes.
89    pub fn tagged(&self) -> Tagged {
90        if self.untagged {
91            Tagged::Untagged
92        } else if let (Some(tag), Some(content)) = (&self.tag, &self.content) {
93            Tagged::Adjacently {
94                tag: tag.clone(),
95                content: content.clone(),
96            }
97        } else if let Some(tag) = &self.tag {
98            Tagged::Internally { tag: tag.clone() }
99        } else {
100            Tagged::Externally
101        }
102    }
103}
104
105/// Serde attributes parsed from a field.
106#[derive(Debug, Clone, Default)]
107pub struct SerdeField {
108    pub rename: Option<String>,
109    pub skip: bool,
110    pub skip_serializing: bool,
111    pub skip_serializing_if: bool,
112    pub skip_deserializing: bool,
113    pub flatten: bool,
114    pub has_default: bool,
115    pub with: bool,
116}
117
118impl SerdeField {
119    /// Parse serde field attributes.
120    pub fn from_attrs(attrs: &[Attribute]) -> Result<Self> {
121        let mut this = Self::default();
122
123        for attr in attrs {
124            if !attr.path().is_ident("serde") {
125                continue;
126            }
127            attr.parse_nested_meta(|meta| {
128                if meta.path.is_ident("rename") {
129                    let value: Lit = meta.value()?.parse()?;
130                    if let Lit::Str(s) = value {
131                        this.rename = Some(s.value());
132                    }
133                } else if meta.path.is_ident("skip") {
134                    this.skip = true;
135                } else if meta.path.is_ident("skip_serializing") {
136                    this.skip_serializing = true;
137                } else if meta.path.is_ident("skip_serializing_if") {
138                    let _ = meta.value().and_then(|v| v.parse::<Lit>()).ok();
139                    this.skip_serializing_if = true;
140                } else if meta.path.is_ident("skip_deserializing") {
141                    this.skip_deserializing = true;
142                } else if meta.path.is_ident("default") {
143                    let _ = meta.value().and_then(|v| v.parse::<Lit>()).ok();
144                    this.has_default = true;
145                } else if meta.path.is_ident("flatten") {
146                    this.flatten = true;
147                } else if meta.path.is_ident("with") || meta.path.is_ident("serialize_with") {
148                    let _ = meta.value().and_then(|v| v.parse::<Lit>()).ok();
149                    this.with = true;
150                } else {
151                    let _ = meta.value().and_then(|v| v.parse::<Lit>()).ok();
152                }
153                Ok(())
154            })?;
155        }
156
157        Ok(this)
158    }
159
160    /// Whether this field may be omitted from serialized output.
161    pub fn maybe_omitted(&self) -> bool {
162        self.skip_serializing || self.skip_serializing_if
163    }
164}
165
166/// Serde attributes parsed from an enum variant.
167#[derive(Debug, Clone, Default)]
168pub struct SerdeVariant {
169    pub rename: Option<String>,
170    pub rename_all: Option<Inflection>,
171    pub skip: bool,
172    pub untagged: bool,
173}
174
175impl SerdeVariant {
176    /// Parse serde variant attributes.
177    pub fn from_attrs(attrs: &[Attribute]) -> Result<Self> {
178        let mut this = Self::default();
179
180        for attr in attrs {
181            if !attr.path().is_ident("serde") {
182                continue;
183            }
184            attr.parse_nested_meta(|meta| {
185                if meta.path.is_ident("rename") {
186                    let value: Lit = meta.value()?.parse()?;
187                    if let Lit::Str(s) = value {
188                        this.rename = Some(s.value());
189                    }
190                } else if meta.path.is_ident("rename_all") {
191                    let value: Lit = meta.value()?.parse()?;
192                    if let Lit::Str(s) = value {
193                        this.rename_all = Inflection::parse(&s.value());
194                    }
195                } else if meta.path.is_ident("skip") {
196                    this.skip = true;
197                } else if meta.path.is_ident("untagged") {
198                    this.untagged = true;
199                } else {
200                    let _ = meta.value().and_then(|v| v.parse::<Lit>()).ok();
201                }
202                Ok(())
203            })?;
204        }
205
206        Ok(this)
207    }
208}