#![warn(
clippy::all,
// clippy::restriction,
clippy::pedantic,
clippy::nursery,
// clippy::cargo
)]
#![recursion_limit = "256"]
extern crate proc_macro;
extern crate syn;
#[macro_use]
extern crate quote;
#[macro_use]
extern crate darling;
use darling::ast;
use darling::FromDeriveInput;
use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::DeriveInput;
#[derive(Debug, FromDeriveInput)]
#[darling(attributes(authorized))]
struct AuthorizedOpts {
ident: syn::Ident,
generics: syn::Generics,
data: ast::Data<(), AuthorizedField>,
scope: String,
}
#[derive(Debug, FromField)]
#[darling(attributes(authorized))]
struct AuthorizedField {
ident: Option<syn::Ident>,
ty: syn::Type,
attrs: Vec<syn::Attribute>,
#[darling(default)]
scope: Option<String>,
#[darling(default)]
default: Option<String>,
}
impl ToTokens for AuthorizedOpts {
fn to_tokens(&self, tokens: &mut TokenStream) {
let struct_name = &self.ident;
let fields = self
.data
.as_ref()
.take_struct()
.expect("Should never be enum")
.fields;
let authorized_trait = generate_authorized_trait(struct_name, &fields);
let authorizable_trait = generate_authorizable_trait(struct_name, &self.scope, &fields);
tokens.extend(quote! {
#authorized_trait
#authorizable_trait
})
}
}
fn generate_authorized_trait(
struct_name: &syn::Ident,
fields: &[&AuthorizedField],
) -> proc_macro2::TokenStream {
let serialize_fields = fields
.iter()
.enumerate()
.map(|(_i, f)| {
let ident = if let Some(ref ident) = f.ident {
ident.clone()
} else {
panic!("");
};
let unauthorized = match &f.default {
None => quote! { Default::default() },
Some(def) => match syn::parse_str::<syn::Path>(def) {
Ok(path) => quote! { #path() },
_ => panic!("Cannot parse default path"),
},
};
let name = format!("{}", ident);
let var_name = syn::Ident::new(&format!("arg_{}", name), ident.span());
quote! {
let #var_name = if !unauthorized_fields.contains(&#name) {
self.#ident.clone()
} else {
#unauthorized
};
}
})
.collect::<Vec<_>>();
let assign_field = fields
.iter()
.enumerate()
.map(|(_i, f)| {
let ident = if let Some(ref ident) = f.ident {
ident.clone()
} else {
panic!("");
};
let name = format!("{}", ident);
let var_name = syn::Ident::new(&format!("arg_{}", name), ident.span());
quote! {
#ident: #var_name
}
})
.collect::<Vec<_>>();
quote! {
impl authorized::Authorized for #struct_name {
type Source = #struct_name;
fn build_serialize_struct<E>(&self,unauthorized_fields: &[&str]) -> Result<Self, E>
{
#(#serialize_fields)*
Ok(Self {
#(#assign_field,)*
})
}
}
}
}
fn generate_authorizable_trait(
struct_name: &syn::Ident,
global_scope: &str,
fields: &[&AuthorizedField],
) -> proc_macro2::TokenStream {
let filtering_fields = fields
.iter()
.enumerate()
.map(|(_i, f)| {
let ident = if let Some(ref ident) = f.ident {
ident.clone()
} else {
panic!("");
};
let name = format!("{}", ident);
if let Some(ref scope) = f.scope {
quote! {
if !#scope.parse::<authorized::scope::Scope>().unwrap().allow_access(scope) {
unauthorized_fields.push(#name);
}
}
} else {
quote! {}
}
})
.collect::<Vec<_>>();
quote! {
impl Authorizable for #struct_name {
type Authorized = #struct_name;
fn filter_unauthorized_fields<'a>(&'a self, scope: &authorized::scope::Scope) -> UnAuthorizedFields<'a>
{
let mut unauthorized_fields = vec![];
#(
#filtering_fields
)*
unauthorized_fields
}
fn authorize<'a>(&'a self, input_scope: &authorized::scope::Scope) -> Result<AuthorizedResult<'a, Self::Authorized>, AuthorizedError> {
let global_scopes = vec!(#global_scope.parse::<Scope>()?);
let unauthorized_fields = self.filter_unauthorized_fields(input_scope);
let status = if global_scopes.iter().map(|scope| scope.allow_access(&input_scope)).any(|access| access) {
AuthorizationStatus::Authorized
} else {
AuthorizationStatus::UnAuthorized
};
let inner = self.build_serialize_struct::<AuthorizedError>(&unauthorized_fields)?;
Ok(AuthorizedResult {
input_scope: input_scope.clone(),
inner,
status,
unauthorized_fields
})
}
}
}
}
#[proc_macro_derive(Authorized, attributes(authorized))]
pub fn derive_authorized(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input: DeriveInput = syn::parse(input).unwrap();
let res = AuthorizedOpts::from_derive_input(&input).unwrap();
proc_macro::TokenStream::from(quote!(#res))
}