use proc_macro2::{Ident, Span}; use syn::spanned::Spanned; use syn::{parse2, FnArg, GenericArgument, LitStr, PathArguments, Type}; use crate::injectable::named_attr_input::NamedAttrInput; use crate::util::error::diagnostic_error_enum; use crate::util::syn_path::SynPathExt; /// Interface for a representation of a dependency of a injectable type. /// /// Found as a argument in the 'new' method of the type. #[cfg_attr(test, mockall::automock)] pub trait IDependency: Sized { /// Build a new `Dependency` from a argument in a 'new' method. fn build(new_method_arg: &FnArg) -> Result; /// Returns the interface type. fn get_interface(&self) -> &Type; /// Returns the pointer type identity. fn get_ptr(&self) -> &Ident; /// Returns optional name of the dependency. fn get_name(&self) -> &Option; } /// Representation of a dependency of a injectable type. /// /// Found as a argument in the 'new' method of the type. #[derive(Debug, PartialEq, Eq)] pub struct Dependency { interface: Type, ptr: Ident, name: Option, } impl IDependency for Dependency { fn build(new_method_arg: &FnArg) -> Result { let typed_new_method_arg = match new_method_arg { FnArg::Typed(typed_arg) => Ok(typed_arg), FnArg::Receiver(receiver_arg) => Err(DependencyError::UnexpectedSelf { self_token_span: receiver_arg.self_token.span, }), }?; let dependency_type_path = match typed_new_method_arg.ty.as_ref() { Type::Path(arg_type_path) => Ok(arg_type_path), Type::Reference(ref_type_path) => match ref_type_path.elem.as_ref() { Type::Path(arg_type_path) => Ok(arg_type_path), other_type => Err(DependencyError::InvalidType { type_span: other_type.span(), }), }, other_type => Err(DependencyError::InvalidType { type_span: other_type.span(), }), }?; let ptr_path_segment = dependency_type_path.path.segments.last().map_or_else( || { Err(DependencyError::MissingType { arg_span: typed_new_method_arg.span(), }) }, Ok, )?; let ptr_ident = ptr_path_segment.ident.clone(); let ptr_generic_args = match ptr_path_segment.arguments.clone() { PathArguments::AngleBracketed(generic_args) => Ok(generic_args), _ => Err(DependencyError::DependencyTypeMissingGenerics { ptr_ident_span: ptr_ident.span(), }), }? .args; let interface = if let Some(GenericArgument::Type(interface)) = ptr_generic_args.first() { Ok(interface.clone()) } else { Err(DependencyError::DependencyTypeMissingGenerics { ptr_ident_span: ptr_ident.span(), }) }?; let arg_attrs = &typed_new_method_arg.attrs; let opt_named_attr = arg_attrs.iter().find(|attr| { attr.path.get_ident().map_or_else( || false, |attr_ident| attr_ident.to_string().as_str() == "named", ) || &attr.path.to_string() == "syrette::named" }); let opt_named_attr_tokens = opt_named_attr.map(|attr| &attr.tokens); let opt_named_attr_input = if let Some(named_attr_tokens) = opt_named_attr_tokens { Some(parse2::(named_attr_tokens.clone()).map_err( |err| DependencyError::InvalidNamedAttrInput { arg_span: typed_new_method_arg.span(), err, }, )?) } else { None }; Ok(Self { interface, ptr: ptr_ident, name: opt_named_attr_input.map(|named_attr_input| named_attr_input.name), }) } fn get_interface(&self) -> &Type { &self.interface } fn get_ptr(&self) -> &Ident { &self.ptr } fn get_name(&self) -> &Option { &self.name } } diagnostic_error_enum! { pub enum DependencyError { #[error("Unexpected 'self' parameter"), span = self_token_span] #[help("Remove the 'self' parameter"), span = self_token_span] UnexpectedSelf { self_token_span: Span }, #[ error("Dependency type must either be a path or a path reference"), span = type_span ] InvalidType { type_span: Span }, #[error("Dependency is missing a type"), span = arg_span] MissingType { arg_span: Span }, #[ error("Expected dependency type to take generic parameters"), span = ptr_ident_span ] DependencyTypeMissingGenerics { ptr_ident_span: Span }, #[error("Dependency has a 'named' attribute given invalid input"), span = arg_span] #[source(err)] InvalidNamedAttrInput { arg_span: Span, err: syn::Error }, } } #[cfg(test)] mod tests { use proc_macro::TokenStream; use proc_macro2::Span; use quote::{format_ident, quote}; use syn::punctuated::Punctuated; use syn::token::{And, Bang, Bracket, Colon, Paren, Pound, SelfValue}; use syn::{ AttrStyle, Attribute, Pat, PatType, PathSegment, Receiver, TypeNever, TypeReference, TypeTuple, }; use super::*; use crate::test_utils; #[test] fn can_build_dependency() { assert!(matches!( Dependency::build(&FnArg::Typed(PatType { attrs: vec![], pat: Box::new(Pat::Verbatim(TokenStream::default().into())), colon_token: Colon::default(), ty: Box::new(test_utils::create_type(test_utils::create_path(&[ test_utils::create_path_segment( format_ident!("TransientPtr"), &[test_utils::create_type(test_utils::create_path(&[ test_utils::create_path_segment(format_ident!("Foo"), &[]) ]))] ), ]))) })), Ok(dependency) if dependency == Dependency { interface: test_utils::create_type(test_utils::create_path(&[ PathSegment::from(format_ident!("Foo")) ])), ptr: format_ident!("TransientPtr"), name: None } )); assert!(matches!( Dependency::build(&FnArg::Typed(PatType { attrs: vec![], pat: Box::new(Pat::Verbatim(TokenStream::default().into())), colon_token: Colon::default(), ty: Box::new(test_utils::create_type(test_utils::create_path(&[ test_utils::create_path_segment(format_ident!("syrette"), &[]), test_utils::create_path_segment(format_ident!("ptr"), &[]), test_utils::create_path_segment( format_ident!("SingletonPtr"), &[test_utils::create_type(test_utils::create_path(&[ test_utils::create_path_segment(format_ident!("Bar"), &[]) ]))] ), ]))) })), Ok(dependency) if dependency == Dependency { interface: test_utils::create_type(test_utils::create_path(&[ PathSegment::from(format_ident!("Bar")) ])), ptr: format_ident!("SingletonPtr"), name: None } )); } #[test] fn can_build_dependency_with_name() { assert!(matches!( Dependency::build(&FnArg::Typed(PatType { attrs: vec![Attribute { pound_token: Pound::default(), style: AttrStyle::Outer, bracket_token: Bracket::default(), path: test_utils::create_path(&[test_utils::create_path_segment( format_ident!("named"), &[] )]), tokens: quote! { ("cool") } }], pat: Box::new(Pat::Verbatim(TokenStream::default().into())), colon_token: Colon::default(), ty: Box::new(test_utils::create_type(test_utils::create_path(&[ test_utils::create_path_segment( format_ident!("TransientPtr"), &[test_utils::create_type(test_utils::create_path(&[ test_utils::create_path_segment(format_ident!("Foo"), &[]) ]))] ), ]))) })), Ok(dependency) if dependency == Dependency { interface: test_utils::create_type(test_utils::create_path(&[ PathSegment::from(format_ident!("Foo")) ])), ptr: format_ident!("TransientPtr"), name: Some(LitStr::new("cool", Span::call_site())) } )); assert!(matches!( Dependency::build(&FnArg::Typed(PatType { attrs: vec![Attribute { pound_token: Pound::default(), style: AttrStyle::Outer, bracket_token: Bracket::default(), path: test_utils::create_path(&[test_utils::create_path_segment( format_ident!("named"), &[] )]), tokens: quote! { ("awesome") } }], pat: Box::new(Pat::Verbatim(TokenStream::default().into())), colon_token: Colon::default(), ty: Box::new(test_utils::create_type(test_utils::create_path(&[ test_utils::create_path_segment(format_ident!("syrette"), &[]), test_utils::create_path_segment(format_ident!("ptr"), &[]), test_utils::create_path_segment( format_ident!("FactoryPtr"), &[test_utils::create_type(test_utils::create_path(&[ test_utils::create_path_segment(format_ident!("Bar"), &[]) ]))] ), ]))) })), Ok(dependency) if dependency == Dependency { interface: test_utils::create_type(test_utils::create_path(&[ PathSegment::from(format_ident!("Bar")) ])), ptr: format_ident!("FactoryPtr"), name: Some(LitStr::new("awesome", Span::call_site())) } )); } #[test] fn cannot_build_dependency_with_receiver_arg() { assert!(Dependency::build(&FnArg::Receiver(Receiver { attrs: vec![], reference: None, mutability: None, self_token: SelfValue::default() })) .is_err()); } #[test] fn cannot_build_dependency_with_type_not_path() { assert!(Dependency::build(&FnArg::Typed(PatType { attrs: vec![], pat: Box::new(Pat::Verbatim(TokenStream::default().into())), colon_token: Colon::default(), ty: Box::new(Type::Tuple(TypeTuple { paren_token: Paren::default(), elems: Punctuated::from_iter(vec![test_utils::create_type( test_utils::create_path(&[test_utils::create_path_segment( format_ident!("EvilType"), &[] )]) )]) })) })) .is_err()); assert!(Dependency::build(&FnArg::Typed(PatType { attrs: vec![], pat: Box::new(Pat::Verbatim(TokenStream::default().into())), colon_token: Colon::default(), ty: Box::new(Type::Reference(TypeReference { and_token: And::default(), lifetime: None, mutability: None, elem: Box::new(Type::Never(TypeNever { bang_token: Bang::default() })) })) })) .is_err()); } #[test] fn cannot_build_dependency_without_generics_args() { assert!(Dependency::build(&FnArg::Typed(PatType { attrs: vec![], pat: Box::new(Pat::Verbatim(TokenStream::default().into())), colon_token: Colon::default(), ty: Box::new(test_utils::create_type(test_utils::create_path(&[ test_utils::create_path_segment(format_ident!("TransientPtr"), &[]), ]))) })) .is_err()); } }