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