1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
use proc_macro2::TokenStream;
use quote::quote;
use std::default::Default;
use syn::{parse_quote, DeriveInput, Ident, LitStr, Path, Visibility};

use super::case_style::CaseStyle;
use super::metadata::{DeriveInputExt, EnumDiscriminantsMeta, EnumMeta};
use super::occurrence_error;

pub trait HasTypeProperties {
    fn get_type_properties(&self) -> syn::Result<StrumTypeProperties>;
}

#[derive(Debug, Clone, Default)]
pub struct StrumTypeProperties {
    pub case_style: Option<CaseStyle>,
    pub ascii_case_insensitive: bool,
    pub crate_module_path: Option<Path>,
    pub discriminant_derives: Vec<Path>,
    pub discriminant_name: Option<Ident>,
    pub discriminant_others: Vec<TokenStream>,
    pub discriminant_vis: Option<Visibility>,
    pub use_phf: bool,
    pub prefix: Option<LitStr>,
    pub enum_repr: Option<TokenStream>,
}

impl HasTypeProperties for DeriveInput {
    fn get_type_properties(&self) -> syn::Result<StrumTypeProperties> {
        let mut output = StrumTypeProperties::default();

        let strum_meta = self.get_metadata()?;
        let discriminants_meta = self.get_discriminants_metadata()?;

        let mut serialize_all_kw = None;
        let mut ascii_case_insensitive_kw = None;
        let mut use_phf_kw = None;
        let mut crate_module_path_kw = None;
        let mut prefix_kw = None;
        for meta in strum_meta {
            match meta {
                EnumMeta::SerializeAll { case_style, kw } => {
                    if let Some(fst_kw) = serialize_all_kw {
                        return Err(occurrence_error(fst_kw, kw, "serialize_all"));
                    }

                    serialize_all_kw = Some(kw);
                    output.case_style = Some(case_style);
                }
                EnumMeta::AsciiCaseInsensitive(kw) => {
                    if let Some(fst_kw) = ascii_case_insensitive_kw {
                        return Err(occurrence_error(fst_kw, kw, "ascii_case_insensitive"));
                    }

                    ascii_case_insensitive_kw = Some(kw);
                    output.ascii_case_insensitive = true;
                }
                EnumMeta::UsePhf(kw) => {
                    if let Some(fst_kw) = use_phf_kw {
                        return Err(occurrence_error(fst_kw, kw, "use_phf"));
                    }

                    use_phf_kw = Some(kw);
                    output.use_phf = true;
                }
                EnumMeta::Crate {
                    crate_module_path,
                    kw,
                } => {
                    if let Some(fst_kw) = crate_module_path_kw {
                        return Err(occurrence_error(fst_kw, kw, "Crate"));
                    }

                    crate_module_path_kw = Some(kw);
                    output.crate_module_path = Some(crate_module_path);
                }
                EnumMeta::Prefix { prefix, kw } => {
                    if let Some(fst_kw) = prefix_kw {
                        return Err(occurrence_error(fst_kw, kw, "prefix"));
                    }

                    prefix_kw = Some(kw);
                    output.prefix = Some(prefix);
                }
            }
        }

        let mut name_kw = None;
        let mut vis_kw = None;
        for meta in discriminants_meta {
            match meta {
                EnumDiscriminantsMeta::Derive { paths, .. } => {
                    output.discriminant_derives.extend(paths);
                }
                EnumDiscriminantsMeta::Name { name, kw } => {
                    if let Some(fst_kw) = name_kw {
                        return Err(occurrence_error(fst_kw, kw, "name"));
                    }

                    name_kw = Some(kw);
                    output.discriminant_name = Some(name);
                }
                EnumDiscriminantsMeta::Vis { vis, kw } => {
                    if let Some(fst_kw) = vis_kw {
                        return Err(occurrence_error(fst_kw, kw, "vis"));
                    }

                    vis_kw = Some(kw);
                    output.discriminant_vis = Some(vis);
                }
                EnumDiscriminantsMeta::Other { path, nested } => {
                    output.discriminant_others.push(quote! { #path(#nested) });
                }
            }
        }

        let attrs = &self.attrs;
        for attr in attrs {
            if let Ok(list) = attr.meta.require_list() {
                if let Some(ident) = list.path.get_ident() {
                    if ident == "repr" {
                        output.enum_repr = Some(list.tokens.clone())
                    }
                }
            }
        }

        Ok(output)
    }
}

impl StrumTypeProperties {
    pub fn crate_module_path(&self) -> Path {
        self.crate_module_path
            .as_ref()
            .map_or_else(|| parse_quote!(::strum), |path| parse_quote!(#path))
    }
}