core_crypto_macros/entity_derive/
parse.rs

1use crate::entity_derive::{Column, ColumnType, Columns, IdColumn, IdColumnType, IdTransformation, KeyStoreEntity};
2use heck::ToSnakeCase;
3use proc_macro2::{Ident, Span};
4use quote::ToTokens;
5use syn::spanned::Spanned;
6use syn::{Attribute, Data, DataStruct, Fields, FieldsNamed, Token, Type};
7
8impl syn::parse::Parse for KeyStoreEntity {
9    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
10        let derive_input = input.parse::<syn::DeriveInput>()?;
11        let struct_name = derive_input.ident.clone();
12
13        // #[entity(collection_name = "my_collection", no_upsert)]
14        let (mut collection_name, no_upsert) = Self::parse_outer_attributes(&derive_input.attrs)?;
15        if collection_name.is_empty() {
16            collection_name = struct_name.to_string().to_snake_case() + "s";
17        }
18
19        let named_fields = Self::fields_from_data(&derive_input.data, derive_input.span())?;
20        let id = IdColumn::parse(named_fields)?;
21        let columns = Columns::parse(named_fields, &id.name)?;
22
23        Ok(KeyStoreEntity {
24            struct_name,
25            collection_name,
26            id,
27            columns,
28            no_upsert,
29        })
30    }
31}
32
33impl KeyStoreEntity {
34    fn parse_outer_attributes(attrs: &[Attribute]) -> Result<(String, bool), syn::Error> {
35        let mut collection_name = String::new();
36        let mut no_upsert = false;
37        for attr in attrs {
38            if !attr.path().is_ident("entity") {
39                continue;
40            }
41            let meta = &attr.meta;
42            let list = meta.require_list()?;
43            list.parse_nested_meta(|meta| {
44                let ident = meta.path.require_ident()?;
45                match ident.to_string().as_str() {
46                    "collection_name" => {
47                        meta.input.parse::<Token![=]>()?;
48                        collection_name = meta.input.parse::<syn::LitStr>()?.value();
49                        Ok(())
50                    }
51                    "no_upsert" => {
52                        no_upsert = true;
53                        Ok(())
54                    }
55                    _ => Err(syn::Error::new_spanned(ident, "unknown argument")),
56                }
57            })?;
58        }
59        Ok((collection_name, no_upsert))
60    }
61
62    fn fields_from_data(data: &Data, span: Span) -> syn::Result<&FieldsNamed> {
63        match data {
64            Data::Struct(DataStruct {
65                fields: Fields::Named(named_fields),
66                ..
67            }) => Ok(named_fields),
68            _ => Err(syn::Error::new(span, "Expected a struct with named fields.")),
69        }
70    }
71}
72
73impl IdColumn {
74    fn parse(named_fields: &FieldsNamed) -> syn::Result<Self> {
75        let mut id = None;
76        let mut implicit_id = None;
77
78        for field in &named_fields.named {
79            let name = field
80                .ident
81                .as_ref()
82                .expect("named fields always have identifiers")
83                .clone();
84
85            if let Some(attr) = field.attrs.iter().find(|a| a.path().is_ident("id")) {
86                let mut column_name = None;
87                let mut transformation = None;
88                let column_type = IdColumnType::parse(&field.ty)?;
89
90                if let Ok(list) = attr.meta.require_list() {
91                    list.parse_nested_meta(|meta| {
92                        match meta.path.require_ident()?.to_string().as_str() {
93                            "column" => {
94                                meta.input.parse::<Token![=]>()?;
95                                column_name = Some(meta.input.parse::<syn::LitStr>()?.value());
96                            }
97                            "hex" => transformation = Some(IdTransformation::Hex),
98                            _ => return Err(syn::Error::new_spanned(meta.path, "unknown argument")),
99                        }
100                        Ok(())
101                    })?;
102                }
103
104                if id
105                    .replace(IdColumn {
106                        name,
107                        column_type,
108                        column_name,
109                        transformation,
110                    })
111                    .is_some()
112                {
113                    return Err(syn::Error::new_spanned(
114                        field,
115                        "Ambiguous `#[id]` attributes. Provide exactly one.",
116                    ));
117                }
118            } else if name == "id" {
119                let column_type = IdColumnType::parse(&field.ty)?;
120                implicit_id = Some(IdColumn {
121                    name,
122                    column_type,
123                    column_name: None,
124                    transformation: None,
125                });
126            }
127        }
128
129        id.or(implicit_id).ok_or_else(|| {
130            syn::Error::new_spanned(
131                named_fields,
132                "No field named `id` or annotated `#[id]` attribute provided.",
133            )
134        })
135    }
136}
137
138impl IdColumnType {
139    fn parse(ty: &Type) -> Result<Self, syn::Error> {
140        let mut type_string = ty.to_token_stream().to_string();
141        type_string.retain(|c| !c.is_whitespace());
142        match type_string.as_str() {
143            "String" | "std::string::String" => Ok(Self::String),
144            "Vec<u8>" | "std::vec::Vec<u8>" => Ok(Self::Bytes),
145            type_string => Err(syn::Error::new_spanned(
146                ty,
147                format!("Expected `String` or `Vec<u8>`, not `{type_string}`."),
148            )),
149        }
150    }
151}
152
153impl Columns {
154    fn parse(named_fields: &FieldsNamed, id_column: &Ident) -> syn::Result<Self> {
155        let columns = named_fields
156            .named
157            .iter()
158            .filter(|field| field.ident.as_ref() != Some(id_column))
159            .map(|field| {
160                let field_name = field
161                    .ident
162                    .as_ref()
163                    .expect("named fields always have identifiers")
164                    .clone();
165                let field_type = ColumnType::parse(&field.ty)?;
166
167                Ok(Column {
168                    name: field_name,
169                    column_type: field_type,
170                })
171            })
172            .collect::<syn::Result<Vec<_>>>()?;
173        if columns.is_empty() {
174            return Err(syn::Error::new_spanned(
175                named_fields,
176                "Provide at least one field to be used as a table column.",
177            ));
178        }
179
180        Ok(Self(columns))
181    }
182}
183
184impl ColumnType {
185    fn parse(ty: &Type) -> Result<Self, syn::Error> {
186        let mut type_string = ty.to_token_stream().to_string();
187        type_string.retain(|c| !c.is_whitespace());
188        match type_string.as_str() {
189            "String" | "std::string::String" => Ok(Self::String),
190            "Vec<u8>" | "std::vec::Vec<u8>" => Ok(Self::Bytes),
191            "Option<Vec<u8>>"
192            | "Option<std::vec::Vec<u8>>"
193            | "core::option::Option<Vec<u8>>"
194            | "core::option::Option<std::vec::Vec<u8>>"
195            | "std::option::Option<Vec<u8>>"
196            | "std::option::Option<std::vec::Vec<u8>>" => Ok(Self::OptionalBytes),
197            type_string => Err(syn::Error::new_spanned(
198                ty,
199                format!("Expected `String`, `Vec<u8>`, or `Option<Vec<u8>>` not `{type_string}`."),
200            )),
201        }
202    }
203}