use proc_macro2::TokenStream;
use quote::quote;
use syn::{
parse_macro_input, Data, DeriveInput, Field, Fields, GenericArgument,
Ident, PathArguments, Type,
};
#[proc_macro_derive(Builder)]
pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let ident = &input.ident;
let generics = &input.generics;
let struct_data = &input.data;
let new_method = new_method(ident, struct_data);
let setters = setters(struct_data);
let tokens = quote! {
impl #generics #ident #generics {
#new_method
#setters
}
};
tokens.into()
}
fn new_method(ident: &Ident, struct_data: &Data) -> TokenStream {
let comment = format!("Constructs a new [`{}`].", ident);
let required_parameters_iter =
required_fields_iter(struct_data).map(|field| {
let ident = &field.ident;
let ty = &field.ty;
quote! { #ident: #ty }
});
let required_parameters = quote! { #(#required_parameters_iter),* };
let fields_iter = fields_iter(struct_data).map(|field| {
let ident = &field.ident;
if is_option(&field.ty) {
quote! { #ident: None }
} else {
quote! { #ident }
}
});
let fields = quote! { #(#fields_iter),* };
quote! {
#[doc = #comment]
pub fn new(#required_parameters) -> Self {
Self {
#fields
}
}
}
}
fn setters(struct_data: &Data) -> TokenStream {
let setters_iter = optional_fields_iter(struct_data).map(|field| {
let ident = field.ident.as_ref().unwrap();
let comment = format!("Sets the {} query string parameter.", ident);
let ty = inner_type(&field.ty);
quote! {
#[doc = #comment]
pub fn #ident(mut self, #ident: #ty) -> Self {
self.#ident = Some(#ident);
self
}
}
});
quote! { #(#setters_iter)* }
}
fn inner_type(ty: &Type) -> &Type {
let first_type_path_segment = match ty {
Type::Path(type_path) => match type_path.path.segments.first() {
Some(path_segment) => path_segment,
None => unimplemented!(),
},
_ => unimplemented!(),
};
match &first_type_path_segment.arguments {
PathArguments::AngleBracketed(arguments) => {
match arguments.args.first() {
Some(GenericArgument::Type(ty)) => ty,
_ => unimplemented!(),
}
}
_ => unimplemented!(),
}
}
fn is_option(ty: &Type) -> bool {
match ty {
Type::Path(type_path) => match type_path.path.segments.first() {
Some(segment) => segment.ident == "Option",
None => false,
},
_ => false,
}
}
fn fields_iter(struct_data: &Data) -> impl Iterator<Item = &Field> {
match struct_data {
Data::Struct(ref data) => match data.fields {
Fields::Named(ref fields) => fields.named.iter(),
_ => unimplemented!(),
},
_ => unimplemented!(),
}
}
fn optional_fields_iter(struct_data: &Data) -> impl Iterator<Item = &Field> {
fields_iter(struct_data).filter(|field| is_option(&field.ty))
}
fn required_fields_iter(struct_data: &Data) -> impl Iterator<Item = &Field> {
fields_iter(struct_data).filter(|field| !is_option(&field.ty))
}