use proc_macro2::TokenStream;
use quote::quote;
use syn::{DeriveInput, Field};
use crate::{consts::FAILED_TO_PARSE_NSG, utils::get_fields};
pub const DERIVE_ID: &str = "Setter";
fn setter_create(fields: &Field) -> TokenStream {
let field_name = fields.ident.as_ref().unwrap();
let field_ty = &fields.ty;
let setter_name = quote::format_ident!("set_{}", field_name);
quote! {
pub fn #setter_name(&mut self, #field_name: #field_ty) {
self.#field_name = #field_name;
}
}
}
pub fn setter(input: TokenStream) -> TokenStream {
let derive_input: DeriveInput = syn::parse2(input).expect(FAILED_TO_PARSE_NSG);
let struct_name = &derive_input.ident;
let fields = match get_fields(struct_name, &derive_input.data, DERIVE_ID) {
Ok(fields) => fields,
Err(e) => {
return e;
}
};
let generics = &derive_input.generics;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let setters = fields.iter().map(|f| setter_create(f));
let expanded = quote! {
impl #impl_generics #struct_name #ty_generics #where_clause {
#(#setters)*
}
};
expanded
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn generates_setters_for_named_fields() {
let input: TokenStream = quote! {
struct User{
id:i32,
name:String,
err:Result<String,String>
}
};
let output = setter(input);
let output_str = output.to_string();
eprintln!("{}", &output_str);
assert!(output_str.contains("impl User"));
assert!(output_str.contains("pub fn set_id (& mut self , id : i32) { self . id = id ; } "));
assert!(
output_str
.contains("pub fn set_name (& mut self , name : String) { self . name = name ; }")
);
assert!(output_str.contains(
"pub fn set_err (& mut self , err : Result < String , String >) { self . err = err ; }"
));
}
#[test]
fn error_on_unnamed_fields() {
let input: TokenStream = quote! {
struct Tuple(i32);
};
let output = setter(input);
let output_str = output.to_string();
assert!(output_str.contains("Setter only supports structs with named fields"));
}
#[test]
fn error_on_non_struct() {
let input: TokenStream = quote! {
enum E {
A,
B,
}
};
let output = setter(input);
let output_str = output.to_string();
assert!(output_str.contains("Setter can only be derived for structs"));
}
#[test]
fn supports_generics() {
let input: TokenStream = quote! {
struct Wrapper<T>
where
T: Clone,
{
value: T,
}
};
let output = setter(input);
let output_str = output.to_string();
eprintln!("{}", &output_str);
assert!(output_str.contains("impl < T > Wrapper < T > where T : Clone"));
assert!(
output_str
.contains("pub fn set_value (& mut self , value : T) { self . value = value ; }")
);
}
#[test]
fn supports_refer() {
let input: TokenStream = quote! {
struct Wrapper<'a>
{
value: &'a str,
}
};
let output = setter(input);
let output_str = output.to_string();
eprintln!("{}", &output_str);
assert!(output_str.contains(
"pub fn set_value (& mut self , value : & 'a str) { self . value = value ; }"
));
}
}