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