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}