strum_macros/helpers/
metadata.rs

1use proc_macro2::TokenStream;
2use syn::{
3    parenthesized,
4    parse::{Parse, ParseStream},
5    parse2, parse_str,
6    punctuated::Punctuated,
7    Attribute, DeriveInput, Expr, ExprLit, Field, Ident, Lit, LitBool, LitStr, Meta, MetaNameValue,
8    Path, Token, Variant, Visibility,
9};
10
11use super::case_style::CaseStyle;
12
13pub mod kw {
14    use syn::custom_keyword;
15    pub use syn::token::Crate;
16
17    // enum metadata
18    custom_keyword!(serialize_all);
19    custom_keyword!(use_phf);
20    custom_keyword!(prefix);
21
22    // enum discriminant metadata
23    custom_keyword!(derive);
24    custom_keyword!(name);
25    custom_keyword!(vis);
26
27    // variant metadata
28    custom_keyword!(message);
29    custom_keyword!(detailed_message);
30    custom_keyword!(serialize);
31    custom_keyword!(to_string);
32    custom_keyword!(disabled);
33    custom_keyword!(default);
34    custom_keyword!(default_with);
35    custom_keyword!(props);
36    custom_keyword!(ascii_case_insensitive);
37}
38
39pub enum EnumMeta {
40    SerializeAll {
41        kw: kw::serialize_all,
42        case_style: CaseStyle,
43    },
44    AsciiCaseInsensitive(kw::ascii_case_insensitive),
45    Crate {
46        kw: kw::Crate,
47        crate_module_path: Path,
48    },
49    UsePhf(kw::use_phf),
50    Prefix {
51        kw: kw::prefix,
52        prefix: LitStr,
53    },
54}
55
56impl Parse for EnumMeta {
57    fn parse(input: ParseStream) -> syn::Result<Self> {
58        let lookahead = input.lookahead1();
59        if lookahead.peek(kw::serialize_all) {
60            let kw = input.parse::<kw::serialize_all>()?;
61            input.parse::<Token![=]>()?;
62            let case_style = input.parse()?;
63            Ok(EnumMeta::SerializeAll { kw, case_style })
64        } else if lookahead.peek(kw::Crate) {
65            let kw = input.parse::<kw::Crate>()?;
66            input.parse::<Token![=]>()?;
67            let path_str: LitStr = input.parse()?;
68            let path_tokens = parse_str(&path_str.value())?;
69            let crate_module_path = parse2(path_tokens)?;
70            Ok(EnumMeta::Crate {
71                kw,
72                crate_module_path,
73            })
74        } else if lookahead.peek(kw::ascii_case_insensitive) {
75            Ok(EnumMeta::AsciiCaseInsensitive(input.parse()?))
76        } else if lookahead.peek(kw::use_phf) {
77            Ok(EnumMeta::UsePhf(input.parse()?))
78        } else if lookahead.peek(kw::prefix) {
79            let kw = input.parse::<kw::prefix>()?;
80            input.parse::<Token![=]>()?;
81            let prefix = input.parse()?;
82            Ok(EnumMeta::Prefix { kw, prefix })
83        } else {
84            Err(lookahead.error())
85        }
86    }
87}
88
89pub enum EnumDiscriminantsMeta {
90    Derive { kw: kw::derive, paths: Vec<Path> },
91    Name { kw: kw::name, name: Ident },
92    Vis { kw: kw::vis, vis: Visibility },
93    Other { path: Path, nested: TokenStream },
94}
95
96impl Parse for EnumDiscriminantsMeta {
97    fn parse(input: ParseStream) -> syn::Result<Self> {
98        if input.peek(kw::derive) {
99            let kw = input.parse()?;
100            let content;
101            parenthesized!(content in input);
102            let paths = content.parse_terminated(Path::parse, Token![,])?;
103            Ok(EnumDiscriminantsMeta::Derive {
104                kw,
105                paths: paths.into_iter().collect(),
106            })
107        } else if input.peek(kw::name) {
108            let kw = input.parse()?;
109            let content;
110            parenthesized!(content in input);
111            let name = content.parse()?;
112            Ok(EnumDiscriminantsMeta::Name { kw, name })
113        } else if input.peek(kw::vis) {
114            let kw = input.parse()?;
115            let content;
116            parenthesized!(content in input);
117            let vis = content.parse()?;
118            Ok(EnumDiscriminantsMeta::Vis { kw, vis })
119        } else {
120            let path = input.parse()?;
121            let content;
122            parenthesized!(content in input);
123            let nested = content.parse()?;
124            Ok(EnumDiscriminantsMeta::Other { path, nested })
125        }
126    }
127}
128
129pub trait DeriveInputExt {
130    /// Get all the strum metadata associated with an enum.
131    fn get_metadata(&self) -> syn::Result<Vec<EnumMeta>>;
132
133    /// Get all the `strum_discriminants` metadata associated with an enum.
134    fn get_discriminants_metadata(&self) -> syn::Result<Vec<EnumDiscriminantsMeta>>;
135}
136
137impl DeriveInputExt for DeriveInput {
138    fn get_metadata(&self) -> syn::Result<Vec<EnumMeta>> {
139        get_metadata_inner("strum", &self.attrs)
140    }
141
142    fn get_discriminants_metadata(&self) -> syn::Result<Vec<EnumDiscriminantsMeta>> {
143        get_metadata_inner("strum_discriminants", &self.attrs)
144    }
145}
146
147pub enum VariantMeta {
148    Message {
149        kw: kw::message,
150        value: LitStr,
151    },
152    DetailedMessage {
153        kw: kw::detailed_message,
154        value: LitStr,
155    },
156    Serialize {
157        kw: kw::serialize,
158        value: LitStr,
159    },
160    Documentation {
161        value: LitStr,
162    },
163    ToString {
164        kw: kw::to_string,
165        value: LitStr,
166    },
167    Disabled(kw::disabled),
168    Default(kw::default),
169    DefaultWith {
170        kw: kw::default_with,
171        value: LitStr,
172    },
173    AsciiCaseInsensitive {
174        kw: kw::ascii_case_insensitive,
175        value: bool,
176    },
177    Props {
178        kw: kw::props,
179        props: Vec<(LitStr, LitStr)>,
180    },
181}
182
183impl Parse for VariantMeta {
184    fn parse(input: ParseStream) -> syn::Result<Self> {
185        let lookahead = input.lookahead1();
186        if lookahead.peek(kw::message) {
187            let kw = input.parse()?;
188            let _: Token![=] = input.parse()?;
189            let value = input.parse()?;
190            Ok(VariantMeta::Message { kw, value })
191        } else if lookahead.peek(kw::detailed_message) {
192            let kw = input.parse()?;
193            let _: Token![=] = input.parse()?;
194            let value = input.parse()?;
195            Ok(VariantMeta::DetailedMessage { kw, value })
196        } else if lookahead.peek(kw::serialize) {
197            let kw = input.parse()?;
198            let _: Token![=] = input.parse()?;
199            let value = input.parse()?;
200            Ok(VariantMeta::Serialize { kw, value })
201        } else if lookahead.peek(kw::to_string) {
202            let kw = input.parse()?;
203            let _: Token![=] = input.parse()?;
204            let value = input.parse()?;
205            Ok(VariantMeta::ToString { kw, value })
206        } else if lookahead.peek(kw::disabled) {
207            Ok(VariantMeta::Disabled(input.parse()?))
208        } else if lookahead.peek(kw::default) {
209            Ok(VariantMeta::Default(input.parse()?))
210        } else if lookahead.peek(kw::default_with) {
211            let kw = input.parse()?;
212            let _: Token![=] = input.parse()?;
213            let value = input.parse()?;
214            Ok(VariantMeta::DefaultWith { kw, value })
215        } else if lookahead.peek(kw::ascii_case_insensitive) {
216            let kw = input.parse()?;
217            let value = if input.peek(Token![=]) {
218                let _: Token![=] = input.parse()?;
219                input.parse::<LitBool>()?.value
220            } else {
221                true
222            };
223            Ok(VariantMeta::AsciiCaseInsensitive { kw, value })
224        } else if lookahead.peek(kw::props) {
225            let kw = input.parse()?;
226            let content;
227            parenthesized!(content in input);
228            let props = content.parse_terminated(Prop::parse, Token![,])?;
229            Ok(VariantMeta::Props {
230                kw,
231                props: props
232                    .into_iter()
233                    .map(|Prop(k, v)| (LitStr::new(&k.to_string(), k.span()), v))
234                    .collect(),
235            })
236        } else {
237            Err(lookahead.error())
238        }
239    }
240}
241
242struct Prop(Ident, LitStr);
243
244impl Parse for Prop {
245    fn parse(input: ParseStream) -> syn::Result<Self> {
246        use syn::ext::IdentExt;
247
248        let k = Ident::parse_any(input)?;
249        let _: Token![=] = input.parse()?;
250        let v = input.parse()?;
251
252        Ok(Prop(k, v))
253    }
254}
255
256pub trait VariantExt {
257    /// Get all the metadata associated with an enum variant.
258    fn get_metadata(&self) -> syn::Result<Vec<VariantMeta>>;
259}
260
261impl VariantExt for Variant {
262    fn get_metadata(&self) -> syn::Result<Vec<VariantMeta>> {
263        let result = get_metadata_inner("strum", &self.attrs)?;
264        self.attrs
265            .iter()
266            .filter(|attr| attr.meta.path().is_ident("doc"))
267            .try_fold(result, |mut vec, attr| {
268                if let Meta::NameValue(MetaNameValue {
269                    value:
270                        Expr::Lit(ExprLit {
271                            lit: Lit::Str(value),
272                            ..
273                        }),
274                    ..
275                }) = &attr.meta
276                {
277                    vec.push(VariantMeta::Documentation {
278                        value: value.clone(),
279                    })
280                }
281                Ok(vec)
282            })
283    }
284}
285
286fn get_metadata_inner<'a, T: Parse>(
287    ident: &str,
288    it: impl IntoIterator<Item = &'a Attribute>,
289) -> syn::Result<Vec<T>> {
290    it.into_iter()
291        .filter(|attr| attr.path().is_ident(ident))
292        .try_fold(Vec::new(), |mut vec, attr| {
293            vec.extend(attr.parse_args_with(Punctuated::<T, Token![,]>::parse_terminated)?);
294            Ok(vec)
295        })
296}
297
298#[derive(Debug)]
299pub enum InnerVariantMeta {
300    DefaultWith { kw: kw::default_with, value: LitStr },
301}
302
303impl Parse for InnerVariantMeta {
304    fn parse(input: ParseStream) -> syn::Result<Self> {
305        let lookahead = input.lookahead1();
306        if lookahead.peek(kw::default_with) {
307            let kw = input.parse()?;
308            let _: Token![=] = input.parse()?;
309            let value = input.parse()?;
310            Ok(InnerVariantMeta::DefaultWith { kw, value })
311        } else {
312            Err(lookahead.error())
313        }
314    }
315}
316
317pub trait InnerVariantExt {
318    /// Get all the metadata associated with an enum variant inner.
319    fn get_named_metadata(&self) -> syn::Result<Vec<InnerVariantMeta>>;
320}
321
322impl InnerVariantExt for Field {
323    fn get_named_metadata(&self) -> syn::Result<Vec<InnerVariantMeta>> {
324        let result = get_metadata_inner("strum", &self.attrs)?;
325        self.attrs
326            .iter()
327            .filter(|attr| attr.meta.path().is_ident("default_with"))
328            .try_fold(result, |vec, _attr| Ok(vec))
329    }
330}