depcon_codegen 0.3.0

Code generation macros for depcon dependency injection framework
Documentation
use crate::utils::import_crate;
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use syn::{spanned::Spanned, Data, DeriveInput, Fields};

pub fn transform(input: DeriveInput) -> TokenStream {
    let crate_path = import_crate();

    let name = input.ident;

    let generics = input.generics;
    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

    let body = inject_body(&input.data);

    let expanded = quote! {
        /// Generated by #[derive(Injectable)]
        impl #impl_generics #crate_path::Injectable for #name #ty_generics #where_clause {
            fn inject(container: &mut #crate_path::Container) -> Result<Self, #crate_path::Error> {
                Ok(Self #body)
            }
        }
    };

    expanded
}

// Generate an expression to inject each field from the DI container.
fn inject_body(data: &Data) -> TokenStream {
    match *data {
        Data::Struct(ref data) => match data.fields {
            Fields::Named(ref fields) => {
                let recurse = fields.named.iter().map(|f| {
                    let name = &f.ident;
                    quote_spanned! {f.span()=>
                        #name: container.resolve()?
                    }
                });
                quote! {
                    { #(#recurse,)* }
                }
            }
            Fields::Unnamed(ref fields) => {
                let recurse = fields.unnamed.iter().map(|f| {
                    quote_spanned! {f.span()=>
                        container.resolve()?
                    }
                });
                quote! {
                    ( #(#recurse),* )
                }
            }
            Fields::Unit => {
                quote!()
            }
        },
        Data::Enum(_) | Data::Union(_) => unimplemented!(),
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_unit_struct() {
        let def = quote!(
            struct Unit;
        );
        let input: DeriveInput = syn::parse2(def).unwrap();
        let actual = transform(input).to_string();
        let expected = quote!(
            /// Generated by #[derive(Injectable)]
            impl depcon::Injectable for Unit {
                fn inject(container: &mut depcon::Container) -> Result<Self, depcon::Error> {
                    Ok(Self)
                }
            }
        )
        .to_string();
        assert_eq!(actual, expected);
    }

    #[test]
    fn test_tuple_struct() {
        let def = quote!(
            struct Tuple(Foo, Bar);
        );
        let input: DeriveInput = syn::parse2(def).unwrap();
        let actual = transform(input).to_string();
        let expected = quote!(
            /// Generated by #[derive(Injectable)]
            impl depcon::Injectable for Tuple {
                fn inject(container: &mut depcon::Container) -> Result<Self, depcon::Error> {
                    Ok(Self(container.resolve()?, container.resolve()?))
                }
            }
        )
        .to_string();
        assert_eq!(actual, expected);
    }

    #[test]
    fn test_named_struct() {
        let def = quote!(
            struct Named {
                foo: Foo,
                bar: Bar,
            }
        );
        let input: DeriveInput = syn::parse2(def).unwrap();
        let actual = transform(input).to_string();
        let expected = quote!(
            /// Generated by #[derive(Injectable)]
            impl depcon::Injectable for Named {
                fn inject(container: &mut depcon::Container) -> Result<Self, depcon::Error> {
                    Ok(Self {
                        foo: container.resolve()?,
                        bar: container.resolve()?,
                    })
                }
            }
        )
        .to_string();
        assert_eq!(actual, expected);
    }
}