athene_macro 0.3.5

Macro generation for athene
Documentation
use crate::controller::handler::{Handler, HandlerAttrs};
use proc_macro2::{Ident, TokenStream};
use syn::{AttributeArgs, Error, ItemImpl, Lit, Meta, MetaNameValue, NestedMeta, Result};

use quote::{quote, ToTokens};

#[derive(Debug)]
pub struct ControllerAttr {
    pub ident: Ident,
    pub name: String,
    pub version: Option<u16>,
    pub prefix: Option<String>,
}

impl ControllerAttr {
    pub fn new(args: AttributeArgs, input: &ItemImpl) -> Result<ControllerAttr> {
        let mut name = None;
        let mut version = None;
        let mut prefix = None;

        let ident = crate::util::parse_item_impl_ident(input)?;

        for m in args.into_iter().filter_map(|a| {
            if let NestedMeta::Meta(m) = a {
                Some(m)
            } else {
                None
            }
        }) {
            match m {
                Meta::Path(p) => {
                    return Err(Error::new_spanned(
                        p,
                        "Unexpected Attribute on controller impl",
                    ));
                }
                Meta::List(l) => {
                    return Err(Error::new_spanned(
                        l,
                        "Unexpected Attribute on controller impl",
                    ));
                }
                Meta::NameValue(MetaNameValue { path, lit, .. }) => match (
                    path.segments
                        .first()
                        .map(|p| p.ident.to_string())
                        .as_deref(),
                    lit,
                ) {
                    (Some("name"), Lit::Str(bp)) => {
                        name = Some(bp.value().trim_matches('/').to_string());
                    }
                    (Some("version"), Lit::Str(v)) => {
                        version = Some(v.value().parse::<u16>().map_err(|_| {
                            Error::new_spanned(
                                v,
                                "Invalid version, expected number between 1 & u16::MAX",
                            )
                        })?);
                    }
                    (Some("version"), Lit::Int(v)) => {
                        version = Some(v.base10_parse::<u16>().map_err(|_| {
                            Error::new_spanned(
                                v,
                                "Invalid version, expected number between 1 & u16::MAX",
                            )
                        })?);
                    }
                    (Some("prefix"), Lit::Str(p)) => {
                        prefix = Some(p.value().trim_matches('/').to_string());
                    }
                    _ => {
                        return Err(Error::new_spanned(
                            path,
                            "Unexpected Param in controller macro",
                        ));
                    }
                },
            }
        }

        let name = name.unwrap_or_else(|| {
            ident
                .to_string()
                .to_lowercase()
                .trim_end_matches("controller")
                .to_string()
        });

        Ok(ControllerAttr {
            ident: ident.clone(),
            name,
            version,
            prefix,
        })
    }
}

pub fn gen_controller_trait_impl(attrs: &ControllerAttr, handlers: &[Handler]) -> TokenStream {
    let base_path = gen_controller_base_path(attrs);
    let handlers_fn = gen_controller_handler_fn(attrs, handlers);

    let ident = &attrs.ident;
    quote! {
        impl Controller for #ident {
            #base_path
            #handlers_fn
        }
    }
}

fn gen_controller_base_path(attr: &ControllerAttr) -> TokenStream {
    let mut path = "/".to_string();

    if let Some(prefix) = attr.prefix.as_ref() {
        path.push_str(prefix);
        path.push('/');
    }

    if let Some(version) = attr.version {
        path.push('v');
        path.push_str(&version.to_string());
        path.push('/');
    }

    path.push_str(attr.name.as_str());

    quote! {
        const BASE_PATH: &'static str = #path;
    }
}

fn gen_controller_handler_fn(attr: &ControllerAttr, handlers: &[Handler]) -> TokenStream {
    let mut handler_stream = TokenStream::new();
    let attr_ident = &attr.ident;

    for handler in handlers {
        let HandlerAttrs {
            method_path: methods_paths,
            ..
        } = &handler.attrs;
        let handler_ident = &handler.method.sig.ident;

        for (method, path) in methods_paths {
            let method = method.as_str();
            let handler_name = handler_ident.to_string();
            (quote! {
                .add_with_name(#handler_name, #path, #method,#attr_ident::#handler_ident)
            })
            .to_tokens(&mut handler_stream);
        }
    }

    quote! {
        fn method(&self) -> Vec<ControllerMethod<Self>> where Self: Sized {
            ControllerBuilder::new()
                #handler_stream
                .build()
        }
    }
}