educe/trait_handlers/into/models/
field_attribute.rs

1use std::collections::HashMap;
2
3use syn::{punctuated::Punctuated, Attribute, Meta, Path, Token};
4
5use crate::{
6    common::{path::meta_2_path, r#type::TypeWithPunctuatedMeta, tools::HashType},
7    panic, Trait,
8};
9
10pub(crate) struct FieldAttribute {
11    pub(crate) types: HashMap<HashType, Option<Path>>,
12}
13
14#[derive(Debug)]
15pub(crate) struct FieldAttributeBuilder {
16    pub(crate) enable_types: bool,
17}
18
19impl FieldAttributeBuilder {
20    pub(crate) fn build_from_into_meta(&self, meta: &[Meta]) -> syn::Result<FieldAttribute> {
21        debug_assert!(!meta.is_empty());
22
23        let mut types = HashMap::new();
24
25        for meta in meta {
26            debug_assert!(meta.path().is_ident("Into"));
27
28            let correct_usage_for_into_attribute = {
29                let mut usage = vec![];
30
31                if self.enable_types {
32                    usage.push(stringify!(#[educe(Into(type))]));
33                    usage.push(stringify!(#[educe(Into(type, method(path_to_method)))]));
34                }
35
36                usage
37            };
38
39            match meta {
40                Meta::Path(_) | Meta::NameValue(_) => {
41                    return Err(panic::attribute_incorrect_format(
42                        meta.path().get_ident().unwrap(),
43                        &correct_usage_for_into_attribute,
44                    ));
45                },
46                Meta::List(list) => {
47                    if !self.enable_types {
48                        return Err(panic::attribute_incorrect_format(
49                            meta.path().get_ident().unwrap(),
50                            &correct_usage_for_into_attribute,
51                        ));
52                    }
53
54                    let TypeWithPunctuatedMeta {
55                        ty,
56                        list: result,
57                    } = list.parse_args()?;
58
59                    let ty = super::super::common::to_hash_type(&ty);
60
61                    let mut method = None;
62                    let mut method_is_set = false;
63
64                    let mut handler = |meta: Meta| -> syn::Result<bool> {
65                        if let Some(ident) = meta.path().get_ident() {
66                            if ident == "method" {
67                                let v = meta_2_path(&meta)?;
68
69                                if method_is_set {
70                                    return Err(panic::parameter_reset(ident));
71                                }
72
73                                method_is_set = true;
74
75                                method = Some(v);
76
77                                return Ok(true);
78                            }
79                        }
80
81                        Ok(false)
82                    };
83
84                    for p in result {
85                        if !handler(p)? {
86                            return Err(panic::attribute_incorrect_format(
87                                meta.path().get_ident().unwrap(),
88                                &correct_usage_for_into_attribute,
89                            ));
90                        }
91                    }
92
93                    if types.contains_key(&ty) {
94                        return Err(super::super::panic::reset_a_type(&ty));
95                    }
96
97                    types.insert(ty, method);
98                },
99            }
100        }
101
102        Ok(FieldAttribute {
103            types,
104        })
105    }
106
107    pub(crate) fn build_from_attributes(
108        &self,
109        attributes: &[Attribute],
110        traits: &[Trait],
111    ) -> syn::Result<FieldAttribute> {
112        let mut output: Option<FieldAttribute> = None;
113
114        let mut v_meta = Vec::new();
115
116        for attribute in attributes.iter() {
117            let path = attribute.path();
118
119            if path.is_ident("educe") {
120                if let Meta::List(list) = &attribute.meta {
121                    let result =
122                        list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
123
124                    for meta in result {
125                        let path = meta.path();
126
127                        let t = match Trait::from_path(path) {
128                            Some(t) => t,
129                            None => return Err(panic::unsupported_trait(meta.path())),
130                        };
131
132                        if !traits.contains(&t) {
133                            return Err(panic::trait_not_used(path.get_ident().unwrap()));
134                        }
135
136                        if t == Trait::Into {
137                            v_meta.push(meta);
138                        }
139                    }
140                }
141            }
142        }
143
144        if !v_meta.is_empty() {
145            output = Some(self.build_from_into_meta(&v_meta)?);
146        }
147
148        Ok(output.unwrap_or_else(|| FieldAttribute {
149            types: HashMap::new()
150        }))
151    }
152}