parker_codegen/lib.rs
1extern crate proc_macro;
2#[macro_use]
3extern crate syn;
4#[macro_use]
5extern crate quote;
6
7use self::proc_macro::TokenStream;
8use syn::{
9 fold::Fold,
10 parse::{Parse, ParseStream, Result as ParseResult},
11 punctuated::Punctuated,
12 Block, FnDecl, ItemFn, LitStr,
13};
14
15/// Parses a list of string constants that represent user roles, separated by
16/// commas.
17struct Args {
18 roles: Vec<String>,
19}
20
21impl Parse for Args {
22 fn parse(input: ParseStream) -> ParseResult<Self> {
23 let roles = Punctuated::<LitStr, Token![,]>::parse_terminated(input)?;
24 Ok(Args {
25 roles: roles.iter().map(|lit| lit.value()).collect(),
26 })
27 }
28}
29
30impl Fold for Args {
31 /// Adds an additional argument to the function signature: `user_roles:
32 /// UserRoles`.
33 fn fold_fn_decl(&mut self, mut i: FnDecl) -> FnDecl {
34 // Add a new argument to the function for user roles
35 i.inputs.push(parse_quote!(user_roles: UserRoles));
36 i
37 }
38
39 /// Wrap the function body in an if/else that verifies that the user has
40 /// all of the roles required to use this endpoint. The incoming block
41 /// is the function body.
42 fn fold_block(&mut self, i: Block) -> Block {
43 let roles = &self.roles; // The roles required by this endpoint
44
45 // Builds an array of string literals, representing the roles to check.
46 // Each of these literals will be checked against the incoming roles
47 // each time a request is served.
48 //
49 // This assumes that the function has a param named `user_roles` of
50 // type `UserRoles`, which it will because we add it ourselves.
51 parse_quote!({
52 if user_roles.has_roles(&[#(#roles),*]) {
53 #i // The normal function body
54 } else {
55 Err(Status::Unauthorized.into()) // Get up on outta here
56 }
57 })
58 }
59}
60
61/// Wraps a route function in a guard that only calls the function body if the
62/// user has sufficient roles. This proc macro must come **before** the route
63/// proc macro!
64#[proc_macro_attribute]
65pub fn auth(args: TokenStream, input: TokenStream) -> TokenStream {
66 let mut args = parse_macro_input!(args as Args);
67 let input = parse_macro_input!(input as ItemFn);
68 let output = args.fold_item_fn(input);
69 TokenStream::from(quote!(#output))
70}