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! {
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
}
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!(
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!(
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!(
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);
}
}