#![deny(missing_docs)]
extern crate proc_macro;
use darling::FromDeriveInput;
use heck::ToLowerCamelCase;
use proc_macro::TokenStream;
use quote::{format_ident, quote, ToTokens};
use syn::{Generics, Ident};
#[derive(FromDeriveInput)]
#[darling(attributes(pandora_request))]
struct PandoraJsonRequest {
ident: Ident,
generics: Generics,
#[darling(default = "std::option::Option::default")]
response_type: Option<String>,
#[darling(default = "std::option::Option::default")]
error_type: Option<String>,
#[darling(default = "std::option::Option::default")]
method_name: Option<String>,
#[darling(default = "std::option::Option::default")]
encrypted: Option<bool>,
}
impl ToTokens for PandoraJsonRequest {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let PandoraJsonRequest {
ref ident,
ref generics,
ref response_type,
ref error_type,
ref method_name,
ref encrypted,
} = *self;
let final_response_type = format_ident!(
"{}",
response_type
.as_ref()
.map(|s| s.to_string())
.unwrap_or_else(|| format!("{ident}Response"))
);
let final_error_type = format_ident!(
"{}",
error_type
.as_ref()
.map(|s| s.to_string())
.unwrap_or_else(|| "Error".to_string())
);
let get_method_decl = if let Some(method_name) = method_name {
quote! {
fn get_method(&self) -> String {
stringify!(#method_name).to_string()
}
}
} else {
let lower_camel_case_method = ident.to_string().to_lower_camel_case();
quote! {
fn get_method(&self) -> String {
let module_name = std::module_path!();
let class_name = module_name.rsplitn(2, "::").next().expect("Could not infer a valid method name since there is no current module. Must pass #[pandora_request(method_name = \"<value>\")] as part of the derive.");
format!("{}.{}", class_name, #lower_camel_case_method)
}
}
};
let encrypt_expr = encrypted
.map(|b| {
quote! {
fn encrypt_request(&self) -> bool {
#b
}
}
})
.unwrap_or_else(|| quote! {});
let (imp, ty, wher) = generics.split_for_impl();
tokens.extend(quote! {
impl #imp PandoraJsonApiRequest for #ident #ty #wher {
type Response = #final_response_type;
type Error = #final_error_type;
#encrypt_expr
#get_method_decl
}
});
}
}
#[proc_macro_derive(PandoraJsonRequest, attributes(pandora_request))]
pub fn derive_pandora_json_request(input: TokenStream) -> TokenStream {
let request = PandoraJsonRequest::from_derive_input(&syn::parse(input).unwrap())
.expect("Failed parsing macro input");
let pm2_tokens: proc_macro2::TokenStream = quote! {#request};
pm2_tokens.into()
}
#[derive(FromDeriveInput)]
#[darling(attributes(pandora_request))]
struct PandoraRestRequest {
ident: Ident,
generics: Generics,
#[darling(default = "std::option::Option::default")]
response_type: Option<String>,
#[darling(default = "std::option::Option::default")]
error_type: Option<String>,
#[darling(default = "std::option::Option::default")]
method_name: Option<String>,
#[darling(default = "std::option::Option::default")]
encrypted: Option<bool>,
}
impl ToTokens for PandoraRestRequest {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let PandoraRestRequest {
ref ident,
ref generics,
ref response_type,
ref error_type,
ref method_name,
ref encrypted,
} = *self;
let final_response_type = format_ident!(
"{}",
response_type
.as_ref()
.map(|s| s.to_string())
.unwrap_or_else(|| format!("{ident}Response"))
);
let final_error_type = format_ident!(
"{}",
error_type
.as_ref()
.map(|s| s.to_string())
.unwrap_or_else(|| "Error".to_string())
);
let get_method_decl = if let Some(method_name) = method_name {
quote! {
fn get_method(&self) -> String {
stringify!(#method_name).to_string()
}
}
} else {
let lower_camel_case_method = ident.to_string().to_lower_camel_case();
quote! {
fn get_method(&self) -> String {
let module_name = std::module_path!();
let class_name = module_name.rsplitn(2, "::").next().expect("Could not infer a valid method name since there is no current module. Must pass #[pandora_request(method_name = \"<value>\")] as part of the derive.");
format!("/api/v1/{}/{}", class_name, #lower_camel_case_method)
}
}
};
let encrypt_expr = encrypted
.map(|b| {
quote! {
fn encrypt_request(&self) -> bool {
#b
}
}
})
.unwrap_or_else(|| quote! {});
let (imp, ty, wher) = generics.split_for_impl();
tokens.extend(quote! {
impl #imp PandoraRestApiRequest for #ident #ty #wher {
type Response = #final_response_type;
type Error = #final_error_type;
#encrypt_expr
#get_method_decl
}
});
}
}
#[proc_macro_derive(PandoraRestRequest, attributes(pandora_request))]
pub fn derive_pandora_rest_request(input: TokenStream) -> TokenStream {
let request = PandoraRestRequest::from_derive_input(&syn::parse(input).unwrap())
.expect("Failed parsing macro input");
let pm2_tokens: proc_macro2::TokenStream = quote! {#request};
pm2_tokens.into()
}