Skip to main content

vercel_rpc_cli/parser/
serde.rs

1use crate::model::{EnumTagging, RenameRule};
2
3/// Walks `#[serde(...)]` attributes and calls `visitor` for each nested meta item.
4/// Returns the last value produced by the visitor, or `None` if no match.
5fn find_serde_meta<T>(
6    attrs: &[syn::Attribute],
7    mut visitor: impl FnMut(&syn::meta::ParseNestedMeta) -> Option<T>,
8) -> Option<T> {
9    let mut result = None;
10    for attr in attrs {
11        if !attr.path().is_ident("serde") {
12            continue;
13        }
14        let _ = attr.parse_nested_meta(|meta| {
15            if let Some(value) = visitor(&meta) {
16                result = Some(value);
17            }
18            Ok(())
19        });
20    }
21    result
22}
23
24/// Parses `#[serde(rename_all = "...")]` from attributes.
25pub fn parse_rename_all(attrs: &[syn::Attribute]) -> Option<RenameRule> {
26    find_serde_meta(attrs, |meta| {
27        if !meta.path.is_ident("rename_all") {
28            return None;
29        }
30        let value = meta.value().ok()?.parse::<syn::LitStr>().ok()?;
31        match value.value().parse::<RenameRule>() {
32            Ok(rule) => Some(rule),
33            Err(e) => {
34                eprintln!(
35                    "warning: unknown rename_all value \"{}\" — {e}; attribute ignored",
36                    value.value(),
37                );
38                None
39            }
40        }
41    })
42}
43
44/// Parses `#[serde(rename = "...")]` from attributes.
45pub fn parse_rename(attrs: &[syn::Attribute]) -> Option<String> {
46    find_serde_meta(attrs, |meta| {
47        if !meta.path.is_ident("rename") {
48            return None;
49        }
50        let value = meta.value().ok()?.parse::<syn::LitStr>().ok()?;
51        Some(value.value())
52    })
53}
54
55/// Checks for `#[serde(skip)]` or `#[serde(skip_serializing)]` on a field.
56pub fn is_skipped(attrs: &[syn::Attribute]) -> bool {
57    find_serde_meta(attrs, |meta| {
58        if meta.path.is_ident("skip") || meta.path.is_ident("skip_serializing") {
59            Some(true)
60        } else {
61            None
62        }
63    })
64    .unwrap_or(false)
65}
66
67/// Parses the serde enum tagging strategy from attributes.
68///
69/// Recognizes `#[serde(tag = "...", content = "...")]` and `#[serde(untagged)]`.
70/// Priority: `untagged` → `Untagged`; `tag` + `content` → `Adjacent`;
71/// `tag` only → `Internal`; else → `External`.
72pub fn parse_enum_tagging(attrs: &[syn::Attribute]) -> EnumTagging {
73    let mut tag = None;
74    let mut content = None;
75    let mut untagged = false;
76
77    for attr in attrs {
78        if !attr.path().is_ident("serde") {
79            continue;
80        }
81        let _ = attr.parse_nested_meta(|meta| {
82            if meta.path.is_ident("untagged") {
83                untagged = true;
84            } else if meta.path.is_ident("tag")
85                && let Ok(value) = meta.value()
86                && let Ok(lit) = value.parse::<syn::LitStr>()
87            {
88                tag = Some(lit.value());
89            } else if meta.path.is_ident("content")
90                && let Ok(value) = meta.value()
91                && let Ok(lit) = value.parse::<syn::LitStr>()
92            {
93                content = Some(lit.value());
94            }
95            Ok(())
96        });
97    }
98
99    if untagged {
100        EnumTagging::Untagged
101    } else if let Some(tag) = tag {
102        if let Some(content) = content {
103            EnumTagging::Adjacent { tag, content }
104        } else {
105            EnumTagging::Internal { tag }
106        }
107    } else {
108        EnumTagging::External
109    }
110}
111
112/// Checks for `#[serde(default)]` on a field.
113pub fn has_default(attrs: &[syn::Attribute]) -> bool {
114    find_serde_meta(attrs, |meta| {
115        if meta.path.is_ident("default") {
116            Some(true)
117        } else {
118            None
119        }
120    })
121    .unwrap_or(false)
122}