Skip to main content

strum_macros/helpers/
metadata.rs

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