rumbok 0.1.0

Lombok-like derive macros (Getter, Setter, Data) for Rust
Documentation
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);
        // impl <T> Wrapper <T> where T: Clone { ... }
        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 ; }"
        ));
    }
}