1use proc_macro::TokenStream;
2use quote::{quote, ToTokens};
3use syn;
4mod parsing;
5use parsing::{MacroInput, get_field_attributes, Fields, AuthType};
6
7#[proc_macro_attribute]
8pub fn model(attr: TokenStream, code: TokenStream) -> TokenStream {
9 let ast: syn::ItemStruct = syn::parse(code).unwrap();
10 let (ast, fields) = get_field_attributes(ast);
11 let new = impl_model(&ast, attr, fields);
12 let mut code: TokenStream = ast.into_token_stream().into();
13 code.extend(new);
14 code
15}
16
17fn impl_model(ast: &syn::ItemStruct, attr: TokenStream, field_attributes: Fields) -> TokenStream {
18 let input = syn::parse_macro_input!(attr as MacroInput);
19 let struct_name = &ast.ident;
20 let prim = field_attributes.prim;
21 let partition = field_attributes.partition;
22 let path = field_attributes.path;
23 let collection = input.collection;
24
25 let verify_prim = quote! {
26 if instance.#prim != #prim {
27 return Err(warp::reject::custom(crate::fault::Fault::IllegalArgument(format!(
28 "{} does not match url ({} != {}).",
29 stringify!(#prim), instance.#prim, #prim
30 ))));
31 }
32 };
33
34 let mut verify = if prim != partition {
35 quote! {
36 if instance.#partition != #partition {
37 return Err(warp::reject::custom(crate::fault::Fault::IllegalArgument(format!(
38 "{} does not match url ({} != {}).",
39 stringify!(#partition), instance.#partition, #partition
40 ))));
41 }
42 }
43 } else {
44 quote!()
45 };
46
47 let (sig_no_prim, sig_prim) = if prim == partition {
48 if path.is_some() {
49 panic!(
50 "If the prim and partition are the same field then no path annotation is allowed"
51 );
52 }
53 (quote!(), quote!(#prim: String,))
54 } else {
55 let mut path_str = quote!();
56 if let Some(path) = path {
57 for s in path {
58 path_str.extend(quote!(#s: String, ));
59 verify.extend(quote! {
60 if instance.#s != #s {
61 return Err(warp::reject::custom(crate::fault::Fault::IllegalArgument(format!(
62 "{} does not match url ({} != {}).",
63 stringify!(#s), instance.#s, #s
64 ))));
65 }
66 });
67 }
68 }
69 (quote!(#partition: String, #path_str), quote!(#prim: String,))
70 };
71
72 let mut get = quote!();
73 let mut post = quote!();
74 let mut put = quote!();
75 let mut delete = quote!();
76 let mut image = quote!();
77 if let Some(tup) = input.get {
78 let authentication = quote_authentication(tup);
79 get = quote! {
80 pub async fn get(#sig_no_prim#sig_prim claims: crate::models::Claims, _v: u8) -> Result<impl warp::Reply, warp::Rejection> {
81 let (instance, _etag): (Self, _) = cosmos_utils::get(#collection, [&#partition], &#prim).await?;
82 #verify_prim
83 #verify
84 #authentication
85
86 Ok(warp::reply::json(&crate::util::DataResponse {
87 data: Some(instance),
88 extra: None::<crate::util::Empty>,
89 }))
90 }
91 };
92 }
93
94 if let Some(tup) = input.post {
95 let authentication = quote_authentication(tup);
96 post = quote! {
97 pub async fn post(#sig_no_prim r: crate::util::DataRequest<Self, crate::util::Empty>, claims: crate::models::Claims, _v: u8) -> Result<impl warp::Reply, warp::Rejection> {
98 let mut instance;
99 if let Some(q) = r.data {
100 instance = q;
101 } else {
102 return Err(warp::reject::custom(crate::fault::Fault::NoData));
103 }
104 #verify
105 #authentication
106 instance.#prim = uuid::Uuid::new_v4().to_string();
107 instance.modified = chrono::Utc::now();
108 cosmos_utils::insert(#collection, [&instance.#partition], &instance, None).await?;
109 Ok(warp::reply::json(&crate::util::DataResponse {
110 data: Some(instance),
111 extra: None::<crate::util::Empty>,
112 }))
113 }
114 };
115 }
116
117 if let Some(tup) = input.put {
118 let authentication = quote_authentication(tup);
119 put = quote! {
120 pub async fn put(#sig_no_prim#sig_prim r: crate::util::DataRequest<Self, crate::util::Empty>, claims: crate::models::Claims, _v: u8) -> Result<impl warp::Reply, warp::Rejection> {
121 let mut new_instance;
122 if let Some(q) = r.data {
123 new_instance = q;
124 } else {
125 return Err(warp::reject::custom(crate::fault::Fault::NoData));
126 }
127 #authentication
128 let instance = cosmos_utils::modify(#collection, [&#partition], &#prim, |old_instance: Self| {
129 let mut instance = new_instance.clone();
130 #verify_prim
131 #verify
132 instance.modified = chrono::Utc::now();
133 Ok(instance)
134 })
135 .await?;
136 Ok(warp::reply::json(&crate::util::DataResponse {
137 data: Some(instance),
138 extra: None::<crate::util::Empty>,
139 }))
140 }
141 };
142 }
143
144 if let Some(tup) = input.delete {
145 let authentication = quote_authentication(tup);
146 delete = quote! {
147 pub async fn delete(#sig_no_prim#sig_prim claims: crate::models::Claims, _v: u8) -> Result<impl warp::Reply, warp::Rejection> {
148 #authentication
149 let instance = cosmos_utils::modify(#collection, [&#partition], &#prim, |mut instance: Self| {
150 #verify_prim
151 #verify
152 instance.deleted = true;
153 instance.modified = chrono::Utc::now();
154 Ok(instance)
155 })
156 .await?;
157 Ok(warp::reply::json(&crate::util::DataResponse {
158 data: Some(instance),
159 extra: None::<crate::util::Empty>,
160 }))
161 }
162 };
163 }
164
165 if let Some(tup) = input.image {
166 let authentication = quote_authentication(tup);
167 image = quote! {
168 pub async fn image(#sig_no_prim#sig_prim claims: crate::models::Claims, _v: u8, f: warp::filters::multipart::FormData) -> Result<impl warp::Reply, warp::Rejection> {
169 #authentication
170 let (mut instance, etag): (Self, _) = cosmos_utils::get(#collection, [&#partition], &#prim).await?;
171 #verify_prim
172 #verify
173 let image_id = cosmos_utils::upload_image(f).await?;
174 instance.images.push(image_id);
175 instance.modified = Utc::now();
176
177 cosmos_utils::upsert(#collection, [&#partition], &instance, Some(&etag)).await?;
178
179 Ok(warp::reply::json(&crate::util::DataResponse {
181 data: Some(instance),
182 extra: None::<crate::util::Empty>,
183 }))
184 }
185 };
186 }
187
188 let gen = quote! {
189 impl #struct_name {
190 #get
191 #post
192 #put
193 #delete
194 #image
195 }
196 };
197 gen.into()
198}
199
200fn quote_authentication(t: Option<AuthType>) -> proc_macro2::TokenStream {
201 match t {
202 Some(AuthType::Flag(flgs, None)) => {
203 quote! {
204 if !crate::util::has_role(None, &claims, #flgs) {
205 return Err(warp::reject::custom(crate::fault::Fault::Forbidden(format!(
206 "Insufficient roles, caller does not have privileges",
207 ))));
208 }
209 }
210 },
211 Some(AuthType::Flag(flgs, Some(res_id))) => {
212 quote! {
213 if !crate::util::has_role(Some(&#res_id), &claims, #flgs) {
214 return Err(warp::reject::custom(crate::fault::Fault::Forbidden(format!(
215 "Insufficient roles, caller does not have privileges for {}", stringify!(#res_id)
216 ))));
217 }
218 }
219 },
220 Some(AuthType::CallingUser(res_id)) => {
221 quote! {
222 if claims.sub != #res_id {
223 return Err(warp::reject::custom(crate::fault::Fault::Forbidden(format!(
224 "Calling user does not have the privilege, {} != {}", claims.sub, stringify!(#res_id)
225 ))));
226 }
227 }
228 },
229 None => {
230 quote! {}
231 }
232 }
233}