1
use proc_macro2::Ident;
2
use std::{collections::HashMap, convert::TryFrom};
3
use syn::{punctuated::Punctuated, Attribute, MetaNameValue, Token};
4

            
5
/// The available annotations for a field that belongs to any struct
6
/// annotaded with `#[canyon_entity]`
7
#[derive(Debug, Clone)]
8
pub enum EntityFieldAnnotation {
9
    PrimaryKey(bool),
10
    ForeignKey(String, String),
11
}
12

            
13
impl EntityFieldAnnotation {
14
    /// Returns the data of the [`EntityFieldAnnotation`] in a understandable format for
15
    /// operations that requires character matching
16
3
    pub fn get_as_string(&self) -> String {
17
3
        match self {
18
2
            Self::PrimaryKey(autoincremental) => {
19
2
                format!("Annotation: PrimaryKey, Autoincremental: {autoincremental}")
20
2
            }
21
1
            Self::ForeignKey(table, column) => {
22
1
                format!("Annotation: ForeignKey, Table: {table}, Column: {column}")
23
1
            }
24
        }
25
3
    }
26

            
27
    /// Retrieves the user defined data in the #[primary_key] attribute
28
4
    fn primary_key_parser(
29
        ident: &Ident,
30
        attr_args: &Result<Punctuated<MetaNameValue, Token![,]>, syn::Error>,
31
    ) -> syn::Result<Self> {
32
4
        match attr_args {
33
            Ok(name_value) => {
34
                let mut data: HashMap<String, bool> = HashMap::new();
35
                for nv in name_value {
36
                    // The identifier
37
                    let attr_value_ident = nv.path.get_ident().unwrap().to_string();
38
                    // The value after the Token[=]
39
                    let attr_value = match &nv.lit {
40
                        // Error if the token is not a boolean literal
41
                        syn::Lit::Bool(v) => v.value(),
42
                        _ => {
43
                            return Err(syn::Error::new_spanned(
44
                                nv.path.clone(),
45
                                format!(
46
                                    "Only bool literals are supported for the `{}` attribute",
47
                                    &attr_value_ident
48
                                ),
49
                            ))
50
                        }
51
                    };
52
                    data.insert(attr_value_ident, attr_value);
53
                }
54

            
55
                Ok(EntityFieldAnnotation::PrimaryKey(
56
                    match data.get("autoincremental") {
57
                        Some(aut) => aut.to_owned(),
58
                        None => {
59
                            // TODO En vez de error, false para default
60
                            return Err(syn::Error::new_spanned(
61
                                ident,
62
                                "Missed `autoincremental` argument on the Primary Key annotation"
63
                                    .to_string(),
64
                            ));
65
                        }
66
                    },
67
                ))
68
            }
69
4
            Err(_) => Ok(EntityFieldAnnotation::PrimaryKey(true)),
70
        }
71
4
    }
72

            
73
4
    fn foreign_key_parser(
74
        ident: &Ident,
75
        attr_args: &Result<Punctuated<MetaNameValue, Token![,]>, syn::Error>,
76
    ) -> syn::Result<Self> {
77
4
        match attr_args {
78
4
            Ok(name_value) => {
79
4
                let mut data: HashMap<String, String> = HashMap::new();
80

            
81
12
                for nv in name_value {
82
                    // The identifier
83
8
                    let attr_value_ident = nv.path.get_ident().unwrap().to_string();
84
                    // The value after the Token[=]
85
8
                    let attr_value = match &nv.lit {
86
                        // Error if the token is not a string literal
87
                        // TODO Implement the option (or change it to) to use a Rust Ident instead a Str Lit
88
8
                        syn::Lit::Str(v) => v.value(),
89
                        _ => {
90
                            return Err(
91
                                syn::Error::new_spanned(
92
                                    nv.path.clone(),
93
                                    format!("Only string literals are supported for the `{attr_value_ident}` attribute")
94
                                )
95
                            )
96
                        }
97
                    };
98
8
                    data.insert(attr_value_ident, attr_value);
99
                }
100

            
101
4
                Ok(EntityFieldAnnotation::ForeignKey(
102
4
                    match data.get("table") {
103
4
                        Some(table) => table.to_owned(),
104
                        None => {
105
                            return Err(syn::Error::new_spanned(
106
                                ident,
107
                                "Missed `table` argument on the Foreign Key annotation".to_string(),
108
                            ))
109
                        }
110
                    },
111
4
                    match data.get("column") {
112
4
                        Some(table) => table.to_owned(),
113
                        None => {
114
                            return Err(syn::Error::new_spanned(
115
                                ident,
116
                                "Missed `column` argument on the Foreign Key annotation"
117
                                    .to_string(),
118
                            ))
119
                        }
120
                    },
121
                ))
122
4
            }
123
            Err(_) => Err(syn::Error::new_spanned(
124
                ident,
125
                "Error generating the Foreign Key".to_string(),
126
            )),
127
        }
128
4
    }
129
}
130

            
131
impl TryFrom<&&Attribute> for EntityFieldAnnotation {
132
    type Error = syn::Error;
133

            
134
8
    fn try_from(attribute: &&Attribute) -> Result<Self, Self::Error> {
135
8
        let ident = attribute.path.segments[0].ident.clone();
136
        let name_values: Result<Punctuated<MetaNameValue, Token![,]>, syn::Error> =
137
8
            attribute.parse_args_with(Punctuated::parse_terminated);
138

            
139
16
        Ok(match ident.to_string().as_str() {
140
8
            "primary_key" => EntityFieldAnnotation::primary_key_parser(&ident, &name_values)?,
141
4
            "foreign_key" => EntityFieldAnnotation::foreign_key_parser(&ident, &name_values)?,
142
            _ => {
143
                return Err(syn::Error::new_spanned(
144
                    ident.clone(),
145
                    format!("Unknown attribute `{}`", &ident),
146
                ))
147
            }
148
        })
149
8
    }
150
}