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}