Skip to main content

rabbithole_derive/
lib.rs

1extern crate proc_macro;
2#[macro_use]
3extern crate thiserror;
4#[macro_use]
5extern crate lazy_static;
6
7mod backend;
8mod error;
9mod field;
10
11use crate::error::EntityDecoratorError;
12use crate::field::{get_field_type, FieldType};
13use proc_macro::TokenStream;
14use quote::{quote, TokenStreamExt};
15use std::collections::HashSet;
16use syn::DeriveInput;
17
18type FieldBundle<'a> =
19    (&'a syn::Ident, Vec<&'a syn::Ident>, Vec<&'a syn::Ident>, Vec<&'a syn::Ident>);
20
21#[proc_macro_derive(EntityDecorator, attributes(entity))]
22pub fn derive(input: TokenStream) -> TokenStream {
23    inner_derive(input).unwrap_or_else(|err| err.to_compile_error()).into()
24}
25
26#[allow(clippy::cognitive_complexity)]
27fn inner_derive(input: TokenStream) -> syn::Result<proc_macro2::TokenStream> {
28    let ast: DeriveInput = syn::parse(input)?;
29    let decorated_struct: &syn::Ident = &ast.ident;
30    let struct_lifetime = &ast.generics;
31
32    let (entity_type, backends) = get_entity_type(&ast)?;
33
34    let (id, attrs, to_ones, to_manys) = get_fields(&ast)?;
35
36    let mut res = quote! {
37        impl #struct_lifetime rabbithole::entity::Entity for #decorated_struct#struct_lifetime {
38            fn included(&self, uri: &str,
39                include_query: &std::option::Option<rabbithole::query::IncludeQuery>,
40                fields_query: &rabbithole::query::FieldsQuery,
41            ) -> rabbithole::RbhResult<rabbithole::model::document::Included> {
42                use rabbithole::entity::SingleEntity;
43                use std::convert::TryInto;
44                let mut included: rabbithole::model::document::Included = Default::default();
45
46                if let Some(included_fields) = include_query {
47                    for inc in included_fields {
48                        if inc.contains('.') {
49                            return Err(rabbithole::model::error::Error::RelationshipPathNotSupported(&inc, None));
50                        }
51                    }
52                }
53                #(
54                    if let Some(included_fields) = include_query {
55                        if included_fields.contains(stringify!(#to_ones)) {
56                            if let Some(inc) = self.#to_ones.to_resource(uri, fields_query) {
57                                included.insert(inc.id.clone(), inc);
58                            }
59                        }
60                    } else {
61                        if let Some(inc) = self.#to_ones.to_resource(uri, fields_query) {
62                            included.insert(inc.id.clone(), inc);
63                        }
64                    }
65                )*
66                #(
67                    if let Some(included_fields) = include_query {
68                        if included_fields.contains(stringify!(#to_manys)) {
69                            for item in &self.#to_manys {
70                                if let Some(inc) = item.to_resource(uri, fields_query) {
71                                    included.insert(inc.id.clone(), inc);
72                                }
73                            }
74                        }
75                    } else {
76                        for item in &self.#to_manys {
77                            if let Some(inc) = item.to_resource(uri, fields_query) {
78                                included.insert(inc.id.clone(), inc);
79                            }
80                        }
81                    }
82                )*
83                Ok(included)
84             }
85
86             fn to_document_automatically(&self, uri: &str, query: &rabbithole::query::Query, request_path: &rabbithole::model::link::RawUri) -> rabbithole::RbhResult<rabbithole::model::document::Document> {
87                 rabbithole::entity::SingleEntity::to_document_automatically(&self, uri, query, request_path)
88             }
89        }
90
91        impl #struct_lifetime rabbithole::entity::SingleEntity for #decorated_struct#struct_lifetime {
92            fn ty() -> std::string::String { #entity_type.to_string() }
93            fn id(&self) -> std::string::String { self.#id.to_string() }
94
95            fn attributes(&self) -> rabbithole::model::resource::Attributes {
96                let mut attr_map: std::collections::HashMap<String, serde_json::Value> = std::default::Default::default();
97                #(  if let Ok(json_value) = serde_json::to_value(self.#attrs.clone()) { attr_map.insert(stringify!(#attrs).to_string(), json_value); } )*
98                attr_map.into()
99            }
100
101            fn relationships(&self, uri: &str) -> rabbithole::model::relationship::Relationships {
102                let mut relat_map: rabbithole::model::relationship::Relationships = std::default::Default::default();
103                #(
104                    if let Some(relat_id) = self.#to_ones.to_resource_identifier() {
105                        let data = rabbithole::model::resource::IdentifierData::Single(Some(relat_id));
106                        let relat = rabbithole::model::relationship::Relationship { data, links: self.to_relationship_links(stringify!(#to_ones), uri), ..std::default::Default::default() };
107                        relat_map.insert(stringify!(#to_ones).to_string(), relat);
108                    }
109                )*
110
111                #(
112                    let mut relat_ids: rabbithole::model::resource::ResourceIdentifiers = std::default::Default::default();
113                    for item in &self.#to_manys {
114                        if let Some(relat_id) = item.to_resource_identifier() {
115                            relat_ids.push(relat_id);
116                        }
117                    }
118                    let data = rabbithole::model::resource::IdentifierData::Multiple(relat_ids);
119                    let relat = rabbithole::model::relationship::Relationship { data, links: self.to_relationship_links(stringify!(#to_manys), uri), ..std::default::Default::default() };
120                    relat_map.insert(stringify!(#to_manys).to_string(), relat);
121                )*
122
123                relat_map
124            }
125        }
126
127
128    };
129
130    for back in backends {
131        if back == "actix" {
132            res.append_all(vec![backend::actix::generate_app(
133                decorated_struct,
134                &entity_type,
135                &to_ones,
136                &to_manys,
137            )]);
138        }
139    }
140
141    Ok(res)
142}
143
144fn get_meta(attrs: &[syn::Attribute]) -> syn::Result<Vec<syn::Meta>> {
145    Ok(attrs
146        .iter()
147        .filter(|a| a.path.is_ident("entity"))
148        .filter_map(|a| {
149            let res = a.parse_meta();
150            res.ok()
151        })
152        .collect::<Vec<syn::Meta>>())
153}
154
155fn get_entity_type(ast: &syn::DeriveInput) -> syn::Result<(String, HashSet<String>)> {
156    let mut ty_opt: Option<String> = None;
157    let mut backends: HashSet<String> = Default::default();
158
159    for meta in get_meta(&ast.attrs)? {
160        if let syn::Meta::List(syn::MetaList { ref nested, .. }) = meta {
161            if let Some(syn::NestedMeta::Meta(ref meta_item)) = nested.last() {
162                match meta_item {
163                    syn::Meta::NameValue(syn::MetaNameValue {
164                        path,
165                        lit: syn::Lit::Str(lit_str),
166                        ..
167                    }) => match path.segments.last() {
168                        Some(syn::PathSegment { ident, .. }) if ident == "type" => {
169                            ty_opt = Some(lit_str.value());
170                        },
171                        _ => {},
172                    },
173                    syn::Meta::List(syn::MetaList { path, nested, .. }) => {
174                        match path.segments.last() {
175                            Some(syn::PathSegment { ident, .. }) if ident == "backend" => {
176                                for nested_backend in nested {
177                                    if let syn::NestedMeta::Meta(syn::Meta::Path(backend_path)) =
178                                        nested_backend
179                                    {
180                                        if let Some(syn::PathSegment { ident, .. }) =
181                                            backend_path.segments.last()
182                                        {
183                                            backends.insert(ident.to_string());
184                                        }
185                                    }
186                                }
187                            },
188                            _ => {},
189                        }
190                    },
191                    _ => {},
192                }
193            }
194        }
195    }
196
197    if let Some(ty) = ty_opt {
198        Ok((ty, backends))
199    } else {
200        Err(syn::Error::new_spanned(ast, EntityDecoratorError::InvalidEntityType))
201    }
202}
203
204fn get_fields(ast: &syn::DeriveInput) -> syn::Result<FieldBundle> {
205    if let syn::Data::Struct(syn::DataStruct {
206        fields: syn::Fields::Named(syn::FieldsNamed { ref named, .. }),
207        ..
208    }) = ast.data
209    {
210        let mut id = None;
211        let mut attrs = vec![];
212        let mut to_ones = vec![];
213        let mut to_manys = vec![];
214
215        for n in named {
216            let f: FieldType = get_field_type(n)?;
217            match (f, n.ident.as_ref()) {
218                (FieldType::Id, Some(ident)) if id.is_none() => id = Some(ident),
219                (FieldType::Id, _) => {
220                    return Err(syn::Error::new_spanned(n, EntityDecoratorError::DuplicatedId))
221                },
222                (FieldType::ToOne, Some(ident)) => to_ones.push(ident),
223                (FieldType::ToMany, Some(ident)) => to_manys.push(ident),
224                (FieldType::Plain, Some(ident)) => {
225                    attrs.push(ident);
226                },
227                _ => {
228                    return Err(syn::Error::new_spanned(n, EntityDecoratorError::FieldWithoutName))
229                },
230            }
231        }
232
233        if let Some(id) = id {
234            return Ok((id, attrs, to_ones, to_manys));
235        }
236    }
237    Err(syn::Error::new_spanned(&ast.ident, EntityDecoratorError::InvalidEntityType))
238}