use proc_macro::TokenStream;
use quote::{quote, ToTokens};
use syn;
mod parsing;
use parsing::{MacroInput, get_field_attributes, Fields, AuthType};
#[proc_macro_attribute]
pub fn model(attr: TokenStream, code: TokenStream) -> TokenStream {
let ast: syn::ItemStruct = syn::parse(code).unwrap();
let (ast, fields) = get_field_attributes(ast);
let new = impl_model(&ast, attr, fields);
let mut code: TokenStream = ast.into_token_stream().into();
code.extend(new);
code
}
fn impl_model(ast: &syn::ItemStruct, attr: TokenStream, field_attributes: Fields) -> TokenStream {
let input = syn::parse_macro_input!(attr as MacroInput);
let struct_name = &ast.ident;
let prim = field_attributes.prim;
let partition = field_attributes.partition;
let path = field_attributes.path;
let collection = input.collection;
let verify_prim = quote! {
if instance.#prim != #prim {
return Err(warp::reject::custom(crate::fault::Fault::IllegalArgument(format!(
"{} does not match url ({} != {}).",
stringify!(#prim), instance.#prim, #prim
))));
}
};
let mut verify = if prim != partition {
quote! {
if instance.#partition != #partition {
return Err(warp::reject::custom(crate::fault::Fault::IllegalArgument(format!(
"{} does not match url ({} != {}).",
stringify!(#partition), instance.#partition, #partition
))));
}
}
} else {
quote!()
};
let (sig_no_prim, sig_prim) = if prim == partition {
if path.is_some() {
panic!(
"If the prim and partition are the same field then no path annotation is allowed"
);
}
(quote!(), quote!(#prim: String,))
} else {
let mut path_str = quote!();
if let Some(path) = path {
for s in path {
path_str.extend(quote!(#s: String, ));
verify.extend(quote! {
if instance.#s != #s {
return Err(warp::reject::custom(crate::fault::Fault::IllegalArgument(format!(
"{} does not match url ({} != {}).",
stringify!(#s), instance.#s, #s
))));
}
});
}
}
(quote!(#partition: String, #path_str), quote!(#prim: String,))
};
let mut get = quote!();
let mut post = quote!();
let mut put = quote!();
let mut delete = quote!();
let mut image = quote!();
if let Some(tup) = input.get {
let authentication = quote_authentication(tup);
get = quote! {
pub async fn get(#sig_no_prim#sig_prim claims: crate::models::Claims, _v: u8) -> Result<impl warp::Reply, warp::Rejection> {
let (instance, _etag): (Self, _) = cosmos_utils::get(#collection, [&#partition], &#prim).await?;
#verify_prim
#verify
#authentication
Ok(warp::reply::json(&crate::util::DataResponse {
data: Some(instance),
extra: None::<crate::util::Empty>,
}))
}
};
}
if let Some(tup) = input.post {
let authentication = quote_authentication(tup);
post = quote! {
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> {
let mut instance;
if let Some(q) = r.data {
instance = q;
} else {
return Err(warp::reject::custom(crate::fault::Fault::NoData));
}
#verify
#authentication
instance.#prim = uuid::Uuid::new_v4().to_string();
instance.modified = chrono::Utc::now();
cosmos_utils::insert(#collection, [&instance.#partition], &instance, None).await?;
Ok(warp::reply::json(&crate::util::DataResponse {
data: Some(instance),
extra: None::<crate::util::Empty>,
}))
}
};
}
if let Some(tup) = input.put {
let authentication = quote_authentication(tup);
put = quote! {
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> {
let mut new_instance;
if let Some(q) = r.data {
new_instance = q;
} else {
return Err(warp::reject::custom(crate::fault::Fault::NoData));
}
#authentication
let instance = cosmos_utils::modify(#collection, [&#partition], &#prim, |old_instance: Self| {
let mut instance = new_instance.clone();
#verify_prim
#verify
instance.modified = chrono::Utc::now();
Ok(instance)
})
.await?;
Ok(warp::reply::json(&crate::util::DataResponse {
data: Some(instance),
extra: None::<crate::util::Empty>,
}))
}
};
}
if let Some(tup) = input.delete {
let authentication = quote_authentication(tup);
delete = quote! {
pub async fn delete(#sig_no_prim#sig_prim claims: crate::models::Claims, _v: u8) -> Result<impl warp::Reply, warp::Rejection> {
#authentication
let instance = cosmos_utils::modify(#collection, [&#partition], &#prim, |mut instance: Self| {
#verify_prim
#verify
instance.deleted = true;
instance.modified = chrono::Utc::now();
Ok(instance)
})
.await?;
Ok(warp::reply::json(&crate::util::DataResponse {
data: Some(instance),
extra: None::<crate::util::Empty>,
}))
}
};
}
if let Some(tup) = input.image {
let authentication = quote_authentication(tup);
image = quote! {
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> {
#authentication
let (mut instance, etag): (Self, _) = cosmos_utils::get(#collection, [&#partition], &#prim).await?;
#verify_prim
#verify
let image_id = cosmos_utils::upload_image(f).await?;
instance.images.push(image_id);
instance.modified = Utc::now();
cosmos_utils::upsert(#collection, [&#partition], &instance, Some(&etag)).await?;
Ok(warp::reply::json(&crate::util::DataResponse {
data: Some(instance),
extra: None::<crate::util::Empty>,
}))
}
};
}
let gen = quote! {
impl #struct_name {
#get
#post
#put
#delete
#image
}
};
gen.into()
}
fn quote_authentication(t: Option<AuthType>) -> proc_macro2::TokenStream {
match t {
Some(AuthType::Flag(flgs, None)) => {
quote! {
if !crate::util::has_role(None, &claims, #flgs) {
return Err(warp::reject::custom(crate::fault::Fault::Forbidden(format!(
"Insufficient roles, caller does not have privileges",
))));
}
}
},
Some(AuthType::Flag(flgs, Some(res_id))) => {
quote! {
if !crate::util::has_role(Some(&#res_id), &claims, #flgs) {
return Err(warp::reject::custom(crate::fault::Fault::Forbidden(format!(
"Insufficient roles, caller does not have privileges for {}", stringify!(#res_id)
))));
}
}
},
Some(AuthType::CallingUser(res_id)) => {
quote! {
if claims.sub != #res_id {
return Err(warp::reject::custom(crate::fault::Fault::Forbidden(format!(
"Calling user does not have the privilege, {} != {}", claims.sub, stringify!(#res_id)
))));
}
}
},
None => {
quote! {}
}
}
}