aboutsummaryrefslogtreecommitdiff
path: root/macros
diff options
context:
space:
mode:
authorHampusM <hampus@hampusmat.com>2022-10-15 18:40:20 +0200
committerHampusM <hampus@hampusmat.com>2022-10-15 18:40:20 +0200
commit740ef47d49e02ae2f2184f4c347d8eba8aee38fd (patch)
tree4a9cbf8f067b6e99f1f95dab774216d804829051 /macros
parentfdbd28fc18a5d2019132413b6699ff7691968fc2 (diff)
refactor: improve internals of macros & add unit tests
Diffstat (limited to 'macros')
-rw-r--r--macros/Cargo.toml3
-rw-r--r--macros/src/fn_trait.rs56
-rw-r--r--macros/src/injectable/dependency.rs337
-rw-r--r--macros/src/injectable/implementation.rs835
-rw-r--r--macros/src/injectable/named_attr_input.rs17
-rw-r--r--macros/src/lib.rs14
-rw-r--r--macros/src/test_utils.rs99
7 files changed, 1139 insertions, 222 deletions
diff --git a/macros/Cargo.toml b/macros/Cargo.toml
index ff55afe..d71971e 100644
--- a/macros/Cargo.toml
+++ b/macros/Cargo.toml
@@ -26,6 +26,9 @@ proc-macro2 = "1.0.40"
uuid = { version = "0.8", features = ["v4"] }
regex = "1.6.0"
once_cell = "1.13.1"
+thiserror = "1.0.37"
[dev_dependencies]
syrette = { version = "0.4.0", path = "..", features = ["factory"] }
+mockall = "0.11.1"
+pretty_assertions = "1.3.0"
diff --git a/macros/src/fn_trait.rs b/macros/src/fn_trait.rs
index a52a00d..d88d391 100644
--- a/macros/src/fn_trait.rs
+++ b/macros/src/fn_trait.rs
@@ -92,22 +92,10 @@ mod tests
use quote::{format_ident, quote};
use syn::token::{Dyn, RArrow};
- use syn::{parse2, Path, PathSegment, TypePath};
+ use syn::{parse2, PathSegment};
use super::*;
-
- fn create_path(segments: &[PathSegment]) -> Path
- {
- Path {
- leading_colon: None,
- segments: segments.iter().cloned().collect(),
- }
- }
-
- fn create_type(path: Path) -> Type
- {
- Type::Path(TypePath { qself: None, path })
- }
+ use crate::test_utils;
#[test]
fn can_parse_fn_trait() -> Result<(), Box<dyn Error>>
@@ -121,15 +109,17 @@ mod tests
trait_ident: format_ident!("Fn"),
paren_token: Paren::default(),
inputs: Punctuated::from_iter(vec![
- create_type(create_path(&[PathSegment::from(format_ident!(
- "String"
- ))])),
- create_type(create_path(&[PathSegment::from(format_ident!("u32"))]))
+ test_utils::create_type(test_utils::create_path(&[
+ PathSegment::from(format_ident!("String"))
+ ])),
+ test_utils::create_type(test_utils::create_path(&[
+ PathSegment::from(format_ident!("u32"))
+ ]))
]),
r_arrow_token: RArrow::default(),
- output: create_type(create_path(&[PathSegment::from(format_ident!(
- "Handle"
- ))])),
+ output: test_utils::create_type(test_utils::create_path(&[
+ PathSegment::from(format_ident!("Handle"))
+ ])),
trait_bounds: Punctuated::new()
}
);
@@ -151,20 +141,20 @@ mod tests
trait_ident: format_ident!("Fn"),
paren_token: Paren::default(),
inputs: Punctuated::from_iter(vec![
- create_type(create_path(&[PathSegment::from(format_ident!(
- "Bread"
- ))])),
- create_type(create_path(&[PathSegment::from(format_ident!(
- "Cheese"
- ))])),
- create_type(create_path(&[PathSegment::from(format_ident!(
- "Tomatoes"
- ))]))
+ test_utils::create_type(test_utils::create_path(&[
+ PathSegment::from(format_ident!("Bread"))
+ ])),
+ test_utils::create_type(test_utils::create_path(&[
+ PathSegment::from(format_ident!("Cheese"))
+ ])),
+ test_utils::create_type(test_utils::create_path(&[
+ PathSegment::from(format_ident!("Tomatoes"))
+ ]))
]),
r_arrow_token: RArrow::default(),
- output: create_type(create_path(&[PathSegment::from(format_ident!(
- "Taco"
- ))])),
+ output: test_utils::create_type(test_utils::create_path(&[
+ PathSegment::from(format_ident!("Taco"))
+ ])),
trait_bounds: Punctuated::new()
}
.into_token_stream()
diff --git a/macros/src/injectable/dependency.rs b/macros/src/injectable/dependency.rs
index 2c5e0fd..314a369 100644
--- a/macros/src/injectable/dependency.rs
+++ b/macros/src/injectable/dependency.rs
@@ -1,56 +1,77 @@
-use std::error::Error;
-
use proc_macro2::Ident;
use syn::{parse2, FnArg, GenericArgument, LitStr, PathArguments, Type};
use crate::injectable::named_attr_input::NamedAttrInput;
use crate::util::syn_path::syn_path_to_string;
+/// 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<Self, DependencyError>;
+
+ /// 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<LitStr>;
+}
+
+/// 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
{
- pub interface: Type,
- pub ptr: Ident,
- pub name: Option<LitStr>,
+ interface: Type,
+ ptr: Ident,
+ name: Option<LitStr>,
}
-impl Dependency
+impl IDependency for Dependency
{
- pub fn build(new_method_arg: &FnArg) -> Result<Self, Box<dyn Error>>
+ fn build(new_method_arg: &FnArg) -> Result<Self, DependencyError>
{
let typed_new_method_arg = match new_method_arg {
FnArg::Typed(typed_arg) => Ok(typed_arg),
- FnArg::Receiver(_) => Err("Unexpected self argument in 'new' method"),
+ FnArg::Receiver(_) => Err(DependencyError::UnexpectedSelfArgument),
}?;
- let ptr_type_path = match typed_new_method_arg.ty.as_ref() {
+ 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),
- &_ => Err("Unexpected reference to non-path type"),
+ &_ => Err(DependencyError::TypeNotPath),
},
- &_ => Err("Expected a path or a reference type"),
+ &_ => Err(DependencyError::TypeNotPath),
}?;
- let ptr_path_segment = ptr_type_path.path.segments.last().map_or_else(
- || Err("Expected pointer type path to have a last segment"),
- Ok,
- )?;
+ let ptr_path_segment = dependency_type_path
+ .path
+ .segments
+ .last()
+ .map_or_else(|| Err(DependencyError::PtrTypePathEmpty), Ok)?;
- let ptr = ptr_path_segment.ident.clone();
+ let ptr_ident = ptr_path_segment.ident.clone();
- let ptr_path_generic_args = &match &ptr_path_segment.arguments {
+ let ptr_generic_args = &match &ptr_path_segment.arguments {
PathArguments::AngleBracketed(generic_args) => Ok(generic_args),
- &_ => Err("Expected pointer type to have a generic type argument"),
+ &_ => Err(DependencyError::PtrTypeNoGenerics),
}?
.args;
- let interface = if let Some(GenericArgument::Type(interface)) =
- ptr_path_generic_args.first()
- {
- Ok(interface.clone())
- } else {
- Err("Expected pointer type to have a generic type argument")
- }?;
+ let interface =
+ if let Some(GenericArgument::Type(interface)) = ptr_generic_args.first() {
+ Ok(interface.clone())
+ } else {
+ Err(DependencyError::PtrTypeNoGenerics)
+ }?;
let arg_attrs = &typed_new_method_arg.attrs;
@@ -63,19 +84,267 @@ impl Dependency
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::<NamedAttrInput>(named_attr_tokens.clone()).map_err(
- |err| format!("Invalid input for 'named' attribute. {}", err),
- )?)
- } else {
- None
- };
+ let opt_named_attr_input = if let Some(named_attr_tokens) = opt_named_attr_tokens
+ {
+ Some(
+ parse2::<NamedAttrInput>(named_attr_tokens.clone())
+ .map_err(DependencyError::ParseNamedAttrInputFailed)?,
+ )
+ } else {
+ None
+ };
Ok(Self {
interface,
- ptr,
+ 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<LitStr>
+ {
+ &self.name
+ }
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum DependencyError
+{
+ #[error("Unexpected 'self' argument in 'new' method")]
+ UnexpectedSelfArgument,
+
+ #[error("Argument type must either be a path or a path reference")]
+ TypeNotPath,
+
+ #[error("Expected pointer type path to not be empty")]
+ PtrTypePathEmpty,
+
+ #[error("Expected pointer type to take generic arguments")]
+ PtrTypeNoGenerics,
+
+ #[error("Failed to parse the input to a 'named' attribute")]
+ ParseNamedAttrInputFailed(#[source] syn::Error),
+}
+
+#[cfg(test)]
+mod tests
+{
+ use std::error::Error;
+
+ 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() -> Result<(), Box<dyn Error>>
+ {
+ assert_eq!(
+ 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"), &[])
+ ]))]
+ ),
+ ])))
+ }))?,
+ Dependency {
+ interface: test_utils::create_type(test_utils::create_path(&[
+ PathSegment::from(format_ident!("Foo"))
+ ])),
+ ptr: format_ident!("TransientPtr"),
+ name: None
+ }
+ );
+
+ assert_eq!(
+ 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"), &[])
+ ]))]
+ ),
+ ])))
+ }))?,
+ Dependency {
+ interface: test_utils::create_type(test_utils::create_path(&[
+ PathSegment::from(format_ident!("Bar"))
+ ])),
+ ptr: format_ident!("SingletonPtr"),
+ name: None
+ }
+ );
+
+ Ok(())
+ }
+
+ #[test]
+ fn can_build_dependency_with_name() -> Result<(), Box<dyn Error>>
+ {
+ assert_eq!(
+ 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"), &[])
+ ]))]
+ ),
+ ])))
+ }))?,
+ 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_eq!(
+ 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"), &[])
+ ]))]
+ ),
+ ])))
+ }))?,
+ 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()))
+ }
+ );
+
+ Ok(())
+ }
+
+ #[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());
+ }
}
diff --git a/macros/src/injectable/implementation.rs b/macros/src/injectable/implementation.rs
index c907f32..9542a98 100644
--- a/macros/src/injectable/implementation.rs
+++ b/macros/src/injectable/implementation.rs
@@ -1,10 +1,11 @@
use std::error::Error;
+use proc_macro2::Ident;
use quote::{format_ident, quote, ToTokens};
use syn::parse::{Parse, ParseStream};
-use syn::{parse_str, ExprMethodCall, FnArg, Generics, ItemImpl, Type};
+use syn::{parse_str, ExprMethodCall, FnArg, Generics, ImplItemMethod, ItemImpl, Type};
-use crate::injectable::dependency::Dependency;
+use crate::injectable::dependency::IDependency;
use crate::util::item_impl::find_impl_method_by_name_mut;
use crate::util::string::camelcase_to_snakecase;
use crate::util::syn_path::syn_path_to_string;
@@ -12,131 +13,144 @@ use crate::util::syn_path::syn_path_to_string;
const DI_CONTAINER_VAR_NAME: &str = "di_container";
const DEPENDENCY_HISTORY_VAR_NAME: &str = "dependency_history";
-pub struct InjectableImpl
+pub struct InjectableImpl<Dep: IDependency>
{
- pub dependencies: Vec<Dependency>,
+ pub dependencies: Vec<Dep>,
pub self_type: Type,
pub generics: Generics,
pub original_impl: ItemImpl,
}
-impl Parse for InjectableImpl
+impl<Dep: IDependency> Parse for InjectableImpl<Dep>
{
+ #[cfg(not(tarpaulin_include))]
fn parse(input: ParseStream) -> syn::Result<Self>
{
- let mut impl_parsed_input = input.parse::<ItemImpl>()?;
+ let input_fork = input.fork();
- let dependencies = Self::build_dependencies(&mut impl_parsed_input)
- .map_err(|err| input.error(err))?;
+ let mut item_impl = input.parse::<ItemImpl>()?;
+
+ let new_method = find_impl_method_by_name_mut(&mut item_impl, "new")
+ .map_or_else(|| Err(input_fork.error("Missing a 'new' method")), Ok)?;
+
+ let dependencies =
+ Self::build_dependencies(new_method).map_err(|err| input.error(err))?;
+
+ Self::remove_method_argument_attrs(new_method);
Ok(Self {
dependencies,
- self_type: impl_parsed_input.self_ty.as_ref().clone(),
- generics: impl_parsed_input.generics.clone(),
- original_impl: impl_parsed_input,
+ self_type: item_impl.self_ty.as_ref().clone(),
+ generics: item_impl.generics.clone(),
+ original_impl: item_impl,
})
}
}
-impl InjectableImpl
+impl<Dep: IDependency> InjectableImpl<Dep>
{
+ #[cfg(not(tarpaulin_include))]
pub fn expand(&self, no_doc_hidden: bool, is_async: bool)
-> proc_macro2::TokenStream
{
- let Self {
- dependencies,
- self_type,
- generics,
- original_impl,
- } = self;
-
let di_container_var = format_ident!("{}", DI_CONTAINER_VAR_NAME);
let dependency_history_var = format_ident!("{}", DEPENDENCY_HISTORY_VAR_NAME);
let maybe_doc_hidden = if no_doc_hidden {
quote! {}
} else {
- quote! {
- #[doc(hidden)]
- }
+ quote! { #[doc(hidden)] }
};
let maybe_prevent_circular_deps = if cfg!(feature = "prevent-circular") {
- quote! {
- if #dependency_history_var.contains(&self_type_name) {
- #dependency_history_var.push(self_type_name);
-
- let dependency_trace =
- syrette::dependency_trace::create_dependency_trace(
- #dependency_history_var.as_slice(),
- self_type_name
- );
-
- return Err(InjectableError::DetectedCircular {dependency_trace });
- }
-
- #dependency_history_var.push(self_type_name);
- }
+ Self::expand_prevent_circular_deps(&dependency_history_var)
} else {
quote! {}
};
let injectable_impl = if is_async {
- let async_get_dep_method_calls =
- Self::create_get_dep_method_calls(dependencies, true);
+ self.expand_async_impl(
+ &maybe_doc_hidden,
+ &di_container_var,
+ &dependency_history_var,
+ &maybe_prevent_circular_deps,
+ &Self::create_get_dep_method_calls(&self.dependencies, true).unwrap(),
+ )
+ } else {
+ self.expand_blocking_impl(
+ &maybe_doc_hidden,
+ &di_container_var,
+ &dependency_history_var,
+ &maybe_prevent_circular_deps,
+ &Self::create_get_dep_method_calls(&self.dependencies, false).unwrap(),
+ )
+ };
- quote! {
- #maybe_doc_hidden
- impl #generics syrette::interfaces::async_injectable::AsyncInjectable for #self_type
- {
- fn resolve<'di_container, 'fut>(
- #di_container_var: &'di_container std::sync::Arc<
- syrette::AsyncDIContainer
- >,
- mut #dependency_history_var: Vec<&'static str>,
- ) -> syrette::future::BoxFuture<
- 'fut,
- Result<
- syrette::ptr::TransientPtr<Self>,
- syrette::errors::injectable::InjectableError
- >
- >
- where
- Self: Sized + 'fut,
- 'di_container: 'fut
- {
- Box::pin(async move {
- use std::any::type_name;
+ let original_impl = &self.original_impl;
- use syrette::errors::injectable::InjectableError;
+ quote! {
+ #original_impl
- let self_type_name = type_name::<#self_type>();
+ #injectable_impl
+ }
+ }
- #maybe_prevent_circular_deps
+ #[cfg(not(tarpaulin_include))]
+ fn expand_prevent_circular_deps(
+ dependency_history_var: &Ident,
+ ) -> proc_macro2::TokenStream
+ {
+ quote! {
+ if #dependency_history_var.contains(&self_type_name) {
+ #dependency_history_var.push(self_type_name);
- Ok(syrette::ptr::TransientPtr::new(Self::new(
- #(#async_get_dep_method_calls),*
- )))
- })
- }
- }
+ let dependency_trace =
+ syrette::dependency_trace::create_dependency_trace(
+ #dependency_history_var.as_slice(),
+ self_type_name
+ );
+ return Err(InjectableError::DetectedCircular {dependency_trace });
}
- } else {
- let get_dep_method_calls =
- Self::create_get_dep_method_calls(dependencies, false);
- quote! {
- #maybe_doc_hidden
- impl #generics syrette::interfaces::injectable::Injectable for #self_type
- {
- fn resolve(
- #di_container_var: &std::rc::Rc<syrette::DIContainer>,
- mut #dependency_history_var: Vec<&'static str>,
- ) -> Result<
+ #dependency_history_var.push(self_type_name);
+ }
+ }
+
+ #[cfg(not(tarpaulin_include))]
+ fn expand_async_impl(
+ &self,
+ maybe_doc_hidden: &proc_macro2::TokenStream,
+ di_container_var: &Ident,
+ dependency_history_var: &Ident,
+ maybe_prevent_circular_deps: &proc_macro2::TokenStream,
+ get_dep_method_calls: &Vec<proc_macro2::TokenStream>,
+ ) -> proc_macro2::TokenStream
+ {
+ let generics = &self.generics;
+ let self_type = &self.self_type;
+
+ quote! {
+ #maybe_doc_hidden
+ impl #generics syrette::interfaces::async_injectable::AsyncInjectable for #self_type
+ {
+ fn resolve<'di_container, 'fut>(
+ #di_container_var: &'di_container std::sync::Arc<
+ syrette::AsyncDIContainer
+ >,
+ mut #dependency_history_var: Vec<&'static str>,
+ ) -> syrette::future::BoxFuture<
+ 'fut,
+ Result<
syrette::ptr::TransientPtr<Self>,
- syrette::errors::injectable::InjectableError>
- {
+ syrette::errors::injectable::InjectableError
+ >
+ >
+ where
+ Self: Sized + 'fut,
+ 'di_container: 'fut
+ {
+ Box::pin(async move {
use std::any::type_name;
use syrette::errors::injectable::InjectableError;
@@ -145,99 +159,147 @@ impl InjectableImpl
#maybe_prevent_circular_deps
- return Ok(syrette::ptr::TransientPtr::new(Self::new(
+ Ok(syrette::ptr::TransientPtr::new(Self::new(
#(#get_dep_method_calls),*
- )));
- }
+ )))
+ })
}
}
- };
+ }
+ }
+
+ #[cfg(not(tarpaulin_include))]
+ fn expand_blocking_impl(
+ &self,
+ maybe_doc_hidden: &proc_macro2::TokenStream,
+ di_container_var: &Ident,
+ dependency_history_var: &Ident,
+ maybe_prevent_circular_deps: &proc_macro2::TokenStream,
+ get_dep_method_calls: &Vec<proc_macro2::TokenStream>,
+ ) -> proc_macro2::TokenStream
+ {
+ let generics = &self.generics;
+ let self_type = &self.self_type;
quote! {
- #original_impl
+ #maybe_doc_hidden
+ impl #generics syrette::interfaces::injectable::Injectable for #self_type
+ {
+ fn resolve(
+ #di_container_var: &std::rc::Rc<syrette::DIContainer>,
+ mut #dependency_history_var: Vec<&'static str>,
+ ) -> Result<
+ syrette::ptr::TransientPtr<Self>,
+ syrette::errors::injectable::InjectableError>
+ {
+ use std::any::type_name;
- #injectable_impl
+ use syrette::errors::injectable::InjectableError;
+
+ let self_type_name = type_name::<#self_type>();
+
+ #maybe_prevent_circular_deps
+
+ return Ok(syrette::ptr::TransientPtr::new(Self::new(
+ #(#get_dep_method_calls),*
+ )));
+ }
+ }
}
}
fn create_get_dep_method_calls(
- dependencies: &[Dependency],
+ dependencies: &[Dep],
is_async: bool,
- ) -> Vec<proc_macro2::TokenStream>
+ ) -> Result<Vec<proc_macro2::TokenStream>, Box<dyn Error>>
{
dependencies
.iter()
.filter_map(|dependency| {
- let dep_interface_str = match &dependency.interface {
- Type::TraitObject(interface_trait) => {
- Some(interface_trait.to_token_stream().to_string())
- }
- Type::Path(path_interface) => {
- Some(syn_path_to_string(&path_interface.path))
- }
- &_ => None,
+ match dependency.get_interface() {
+ Type::TraitObject(_) | Type::Path(_) => Some(()),
+ _ => None,
}?;
- let method_call = parse_str::<ExprMethodCall>(
- format!(
- "{}.get_bound::<{}>({}.clone(), {})",
- DI_CONTAINER_VAR_NAME,
- dep_interface_str,
- DEPENDENCY_HISTORY_VAR_NAME,
- dependency.name.as_ref().map_or_else(
- || "None".to_string(),
- |name| format!("Some(\"{}\")", name.value())
- )
- )
- .as_str(),
- )
- .ok()?;
-
- Some((method_call, dependency))
- })
- .map(|(method_call, dep_type)| {
- let ptr_name = dep_type.ptr.to_string();
-
- let to_ptr = format_ident!(
- "{}",
- camelcase_to_snakecase(&ptr_name.replace("Ptr", ""))
- );
-
- let do_method_call = if is_async {
- quote! { #method_call.await }
- } else {
- quote! { #method_call }
- };
-
- let resolve_failed_error = if is_async {
- quote! { InjectableError::AsyncResolveFailed }
- } else {
- quote! { InjectableError::ResolveFailed }
- };
-
- quote! {
- #do_method_call.map_err(|err| #resolve_failed_error {
- reason: Box::new(err),
- affected: self_type_name
- })?.#to_ptr().unwrap()
- }
+ Some(Self::create_single_get_dep_method_call(
+ dependency, is_async,
+ ))
})
.collect()
}
- fn build_dependencies(
- item_impl: &mut ItemImpl,
- ) -> Result<Vec<Dependency>, Box<dyn Error>>
+ fn create_single_get_dep_method_call(
+ dependency: &Dep,
+ is_async: bool,
+ ) -> Result<proc_macro2::TokenStream, Box<dyn Error>>
+ {
+ let dep_interface_str = match &dependency.get_interface() {
+ Type::TraitObject(interface_trait) => {
+ Ok(interface_trait.to_token_stream().to_string())
+ }
+ Type::Path(path_interface) => Ok(syn_path_to_string(&path_interface.path)),
+ &_ => Err("Invalid type. Expected trait type or path type"),
+ }?;
+
+ let method_call = parse_str::<ExprMethodCall>(
+ format!(
+ "{}.get_bound::<{}>({}.clone(), {})",
+ DI_CONTAINER_VAR_NAME,
+ dep_interface_str,
+ DEPENDENCY_HISTORY_VAR_NAME,
+ dependency.get_name().as_ref().map_or_else(
+ || "None".to_string(),
+ |name| format!("Some(\"{}\")", name.value())
+ )
+ )
+ .as_str(),
+ )?;
+
+ let ptr_name = dependency.get_ptr().to_string();
+
+ let to_ptr =
+ format_ident!("{}", camelcase_to_snakecase(&ptr_name.replace("Ptr", "")));
+
+ let do_method_call = if is_async {
+ quote! { #method_call.await }
+ } else {
+ quote! { #method_call }
+ };
+
+ let resolve_failed_error = if is_async {
+ quote! { InjectableError::AsyncResolveFailed }
+ } else {
+ quote! { InjectableError::ResolveFailed }
+ };
+
+ Ok(quote! {
+ #do_method_call.map_err(|err| #resolve_failed_error {
+ reason: Box::new(err),
+ affected: self_type_name
+ })?.#to_ptr().unwrap()
+ })
+ }
+
+ fn build_dependencies(new_method: &ImplItemMethod)
+ -> Result<Vec<Dep>, Box<dyn Error>>
{
- let new_method_impl_item = find_impl_method_by_name_mut(item_impl, "new")
- .map_or_else(|| Err("Missing a 'new' method"), Ok)?;
+ let new_method_args = &new_method.sig.inputs;
- let new_method_args = &mut new_method_impl_item.sig.inputs;
+ let dependencies_result: Result<Vec<_>, _> =
+ new_method_args.iter().map(Dep::build).collect();
- let dependencies: Result<Vec<_>, _> =
- new_method_args.iter().map(Dependency::build).collect();
+ let deps = dependencies_result?;
- for arg in new_method_args {
+ Ok(deps)
+ }
+
+ // Removes argument attributes from a method, as they are not actually valid Rust.
+ // Not doing this would cause a compilation error.
+ fn remove_method_argument_attrs(method: &mut ImplItemMethod)
+ {
+ let method_args = &mut method.sig.inputs;
+
+ for arg in method_args {
let typed_arg = if let FnArg::Typed(typed_arg) = arg {
typed_arg
} else {
@@ -265,7 +327,476 @@ impl InjectableImpl
typed_arg.attrs.remove(attr_index);
}
}
+ }
+}
+
+#[cfg(test)]
+mod tests
+{
+ use std::sync::{Mutex, MutexGuard};
+
+ use once_cell::sync::Lazy;
+ use pretty_assertions::assert_eq;
+ use proc_macro2::{Span, TokenStream};
+ use syn::token::{Brace, Bracket, Colon, Paren, Pound};
+ use syn::{
+ parse2,
+ AttrStyle,
+ Attribute,
+ Block,
+ ImplItemMethod,
+ LitStr,
+ Pat,
+ PatType,
+ Visibility,
+ };
+
+ use super::*;
+ use crate::injectable::dependency::MockIDependency;
+ use crate::injectable::named_attr_input::NamedAttrInput;
+ use crate::test_utils;
+
+ static TEST_MUTEX: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
+
+ // When a test panics, it will poison the Mutex. Since we don't actually
+ // care about the state of the data we ignore that it is poisoned and grab
+ // the lock regardless. If you just do `let _lock = &TEST_MUTEX.lock().unwrap()`, one
+ // test panicking will cause all other tests that try and acquire a lock on
+ // that Mutex to also panic.
+ fn get_lock(m: &'static Mutex<()>) -> MutexGuard<'static, ()>
+ {
+ match m.lock() {
+ Ok(guard) => guard,
+ Err(poisoned) => poisoned.into_inner(),
+ }
+ }
- dependencies
+ #[test]
+ fn can_build_dependencies() -> Result<(), Box<dyn Error>>
+ {
+ let method = ImplItemMethod {
+ attrs: vec![],
+ vis: Visibility::Inherited,
+ defaultness: None,
+ sig: test_utils::create_signature(
+ format_ident!("new"),
+ vec![
+ (
+ 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"),
+ &[],
+ ),
+ ]))],
+ ),
+ ])),
+ vec![],
+ ),
+ (
+ test_utils::create_type(test_utils::create_path(&[
+ test_utils::create_path_segment(
+ format_ident!("FactoryPtr"),
+ &[test_utils::create_type(test_utils::create_path(&[
+ test_utils::create_path_segment(
+ format_ident!("BarFactory"),
+ &[],
+ ),
+ ]))],
+ ),
+ ])),
+ vec![],
+ ),
+ ],
+ test_utils::create_type(test_utils::create_path(&[
+ test_utils::create_path_segment(format_ident!("Self"), &[]),
+ ])),
+ ),
+ block: Block {
+ brace_token: Brace::default(),
+ stmts: vec![],
+ },
+ };
+
+ let _lock = get_lock(&TEST_MUTEX);
+
+ let build_context = MockIDependency::build_context();
+
+ build_context
+ .expect()
+ .returning(|_| Ok(MockIDependency::new()));
+
+ let dependencies =
+ InjectableImpl::<MockIDependency>::build_dependencies(&method)?;
+
+ assert_eq!(dependencies.len(), 2);
+
+ Ok(())
+ }
+
+ #[test]
+ fn can_build_named_dependencies() -> Result<(), Box<dyn Error>>
+ {
+ let method = ImplItemMethod {
+ attrs: vec![],
+ vis: Visibility::Inherited,
+ defaultness: None,
+ sig: test_utils::create_signature(
+ format_ident!("new"),
+ vec![
+ (
+ 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"),
+ &[],
+ ),
+ ]))],
+ ),
+ ])),
+ vec![],
+ ),
+ (
+ test_utils::create_type(test_utils::create_path(&[
+ test_utils::create_path_segment(
+ format_ident!("FactoryPtr"),
+ &[test_utils::create_type(test_utils::create_path(&[
+ test_utils::create_path_segment(
+ format_ident!("BarFactory"),
+ &[],
+ ),
+ ]))],
+ ),
+ ])),
+ 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: NamedAttrInput {
+ paren: Paren::default(),
+ name: LitStr::new("awesome", Span::call_site()),
+ }
+ .to_token_stream(),
+ }],
+ ),
+ ],
+ test_utils::create_type(test_utils::create_path(&[
+ test_utils::create_path_segment(format_ident!("Self"), &[]),
+ ])),
+ ),
+ block: Block {
+ brace_token: Brace::default(),
+ stmts: vec![],
+ },
+ };
+
+ let _lock = get_lock(&TEST_MUTEX);
+
+ let build_context = MockIDependency::build_context();
+
+ build_context
+ .expect()
+ .returning(|_| Ok(MockIDependency::new()))
+ .times(2);
+
+ let dependencies =
+ InjectableImpl::<MockIDependency>::build_dependencies(&method)?;
+
+ assert_eq!(dependencies.len(), 2);
+
+ Ok(())
+ }
+
+ #[test]
+ fn can_remove_method_argument_attrs()
+ {
+ let first_arg_type = 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"), &[]),
+ ]))],
+ ),
+ ]));
+
+ let second_arg_type = test_utils::create_type(test_utils::create_path(&[
+ test_utils::create_path_segment(
+ format_ident!("FactoryPtr"),
+ &[test_utils::create_type(test_utils::create_path(&[
+ test_utils::create_path_segment(format_ident!("BarFactory"), &[]),
+ ]))],
+ ),
+ ]));
+
+ let mut method = ImplItemMethod {
+ attrs: vec![],
+ vis: Visibility::Inherited,
+ defaultness: None,
+ sig: test_utils::create_signature(
+ format_ident!("new"),
+ vec![
+ (
+ first_arg_type.clone(),
+ 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: NamedAttrInput {
+ paren: Paren::default(),
+ name: LitStr::new("cool", Span::call_site()),
+ }
+ .to_token_stream(),
+ }],
+ ),
+ (
+ second_arg_type.clone(),
+ 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: NamedAttrInput {
+ paren: Paren::default(),
+ name: LitStr::new("awesome", Span::call_site()),
+ }
+ .to_token_stream(),
+ }],
+ ),
+ ],
+ test_utils::create_type(test_utils::create_path(&[
+ test_utils::create_path_segment(format_ident!("Self"), &[]),
+ ])),
+ ),
+ block: Block {
+ brace_token: Brace::default(),
+ stmts: vec![],
+ },
+ };
+
+ InjectableImpl::<MockIDependency>::remove_method_argument_attrs(&mut method);
+
+ assert_eq!(
+ method.sig.inputs.first().unwrap().clone(),
+ FnArg::Typed(PatType {
+ attrs: vec![],
+ pat: Box::new(Pat::Verbatim(TokenStream::new())),
+ colon_token: Colon::default(),
+ ty: Box::new(first_arg_type),
+ })
+ );
+
+ assert_eq!(
+ method.sig.inputs.last().unwrap().clone(),
+ FnArg::Typed(PatType {
+ attrs: vec![],
+ pat: Box::new(Pat::Verbatim(TokenStream::new())),
+ colon_token: Colon::default(),
+ ty: Box::new(second_arg_type),
+ })
+ );
+ }
+
+ #[test]
+ fn can_create_single_get_dep_method_call() -> Result<(), Box<dyn Error>>
+ {
+ let mut mock_dependency = MockIDependency::new();
+
+ mock_dependency
+ .expect_get_interface()
+ .return_const(test_utils::create_type(test_utils::create_path(&[
+ test_utils::create_path_segment(format_ident!("Foo"), &[]),
+ ])));
+
+ mock_dependency.expect_get_name().return_const(None);
+
+ mock_dependency
+ .expect_get_ptr()
+ .return_const(format_ident!("TransientPtr"));
+
+ let di_container_var_ident = format_ident!("{}", DI_CONTAINER_VAR_NAME);
+ let dep_history_var_ident = format_ident!("{}", DEPENDENCY_HISTORY_VAR_NAME);
+
+ let output =
+ InjectableImpl::<MockIDependency>::create_single_get_dep_method_call(
+ &mock_dependency,
+ false,
+ )?;
+
+ assert_eq!(
+ parse2::<ExprMethodCall>(output)?,
+ parse2::<ExprMethodCall>(quote! {
+ #di_container_var_ident
+ .get_bound::<Foo>(#dep_history_var_ident.clone(), None)
+ .map_err(|err| InjectableError::ResolveFailed {
+ reason: Box::new(err),
+ affected: self_type_name
+ })?
+ .transient()
+ .unwrap()
+
+ })?
+ );
+
+ Ok(())
+ }
+
+ #[test]
+ fn can_create_single_get_dep_method_call_with_name() -> Result<(), Box<dyn Error>>
+ {
+ let mut mock_dependency = MockIDependency::new();
+
+ mock_dependency
+ .expect_get_interface()
+ .return_const(test_utils::create_type(test_utils::create_path(&[
+ test_utils::create_path_segment(format_ident!("Foo"), &[]),
+ ])));
+
+ mock_dependency
+ .expect_get_name()
+ .return_const(Some(LitStr::new("special", Span::call_site())));
+
+ mock_dependency
+ .expect_get_ptr()
+ .return_const(format_ident!("TransientPtr"));
+
+ let di_container_var_ident = format_ident!("{}", DI_CONTAINER_VAR_NAME);
+ let dep_history_var_ident = format_ident!("{}", DEPENDENCY_HISTORY_VAR_NAME);
+
+ let output =
+ InjectableImpl::<MockIDependency>::create_single_get_dep_method_call(
+ &mock_dependency,
+ false,
+ )?;
+
+ assert_eq!(
+ parse2::<ExprMethodCall>(output)?,
+ parse2::<ExprMethodCall>(quote! {
+ #di_container_var_ident
+ .get_bound::<Foo>(#dep_history_var_ident.clone(), Some("special"))
+ .map_err(|err| InjectableError::ResolveFailed {
+ reason: Box::new(err),
+ affected: self_type_name
+ })?
+ .transient()
+ .unwrap()
+
+ })?
+ );
+
+ Ok(())
+ }
+
+ #[test]
+ fn can_create_single_get_dep_method_call_async() -> Result<(), Box<dyn Error>>
+ {
+ let mut mock_dependency = MockIDependency::new();
+
+ mock_dependency
+ .expect_get_interface()
+ .return_const(test_utils::create_type(test_utils::create_path(&[
+ test_utils::create_path_segment(format_ident!("Foo"), &[]),
+ ])));
+
+ mock_dependency.expect_get_name().return_const(None);
+
+ mock_dependency
+ .expect_get_ptr()
+ .return_const(format_ident!("TransientPtr"));
+
+ let di_container_var_ident = format_ident!("{}", DI_CONTAINER_VAR_NAME);
+ let dep_history_var_ident = format_ident!("{}", DEPENDENCY_HISTORY_VAR_NAME);
+
+ let output =
+ InjectableImpl::<MockIDependency>::create_single_get_dep_method_call(
+ &mock_dependency,
+ true,
+ )?;
+
+ assert_eq!(
+ parse2::<ExprMethodCall>(output)?,
+ parse2::<ExprMethodCall>(quote! {
+ #di_container_var_ident
+ .get_bound::<Foo>(#dep_history_var_ident.clone(), None)
+ .await
+ .map_err(|err| InjectableError::AsyncResolveFailed {
+ reason: Box::new(err),
+ affected: self_type_name
+ })?
+ .transient()
+ .unwrap()
+
+ })?
+ );
+
+ Ok(())
+ }
+
+ #[test]
+ fn can_create_single_get_dep_method_call_async_with_name(
+ ) -> Result<(), Box<dyn Error>>
+ {
+ let mut mock_dependency = MockIDependency::new();
+
+ mock_dependency
+ .expect_get_interface()
+ .return_const(test_utils::create_type(test_utils::create_path(&[
+ test_utils::create_path_segment(format_ident!("Foo"), &[]),
+ ])));
+
+ mock_dependency
+ .expect_get_name()
+ .return_const(Some(LitStr::new("foobar", Span::call_site())));
+
+ mock_dependency
+ .expect_get_ptr()
+ .return_const(format_ident!("TransientPtr"));
+
+ let di_container_var_ident = format_ident!("{}", DI_CONTAINER_VAR_NAME);
+ let dep_history_var_ident = format_ident!("{}", DEPENDENCY_HISTORY_VAR_NAME);
+
+ let output =
+ InjectableImpl::<MockIDependency>::create_single_get_dep_method_call(
+ &mock_dependency,
+ true,
+ )?;
+
+ assert_eq!(
+ parse2::<ExprMethodCall>(output)?,
+ parse2::<ExprMethodCall>(quote! {
+ #di_container_var_ident
+ .get_bound::<Foo>(#dep_history_var_ident.clone(), Some("foobar"))
+ .await
+ .map_err(|err| InjectableError::AsyncResolveFailed {
+ reason: Box::new(err),
+ affected: self_type_name
+ })?
+ .transient()
+ .unwrap()
+
+ })?
+ );
+
+ Ok(())
}
}
diff --git a/macros/src/injectable/named_attr_input.rs b/macros/src/injectable/named_attr_input.rs
index 5f7123c..eaa9dbf 100644
--- a/macros/src/injectable/named_attr_input.rs
+++ b/macros/src/injectable/named_attr_input.rs
@@ -1,8 +1,11 @@
+use quote::ToTokens;
use syn::parse::Parse;
+use syn::token::Paren;
use syn::{parenthesized, LitStr};
pub struct NamedAttrInput
{
+ pub paren: Paren,
pub name: LitStr,
}
@@ -12,10 +15,22 @@ impl Parse for NamedAttrInput
{
let content;
- parenthesized!(content in input);
+ let paren = parenthesized!(content in input);
Ok(Self {
+ paren,
name: content.parse()?,
})
}
}
+
+impl ToTokens for NamedAttrInput
+{
+ fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream)
+ {
+ self.paren
+ .surround(&mut self.name.to_token_stream(), |stream| {
+ stream.to_tokens(tokens);
+ });
+ }
+}
diff --git a/macros/src/lib.rs b/macros/src/lib.rs
index 293ac06..298a717 100644
--- a/macros/src/lib.rs
+++ b/macros/src/lib.rs
@@ -1,4 +1,5 @@
#![cfg_attr(doc_cfg, feature(doc_cfg))]
+#![cfg_attr(test, feature(is_some_with))]
#![deny(clippy::all)]
#![deny(clippy::pedantic)]
#![allow(clippy::module_name_repetitions)]
@@ -24,7 +25,11 @@ mod factory;
#[cfg_attr(doc_cfg, doc(cfg(feature = "factory")))]
mod fn_trait;
+#[cfg(test)]
+mod test_utils;
+
use crate::declare_interface_args::DeclareInterfaceArgs;
+use crate::injectable::dependency::Dependency;
use crate::injectable::implementation::InjectableImpl;
use crate::injectable::macro_args::InjectableMacroArgs;
use crate::libs::intertrait_macros::gen_caster::generate_caster;
@@ -112,6 +117,7 @@ use crate::libs::intertrait_macros::gen_caster::generate_caster;
/// [`AsyncDIContainer`]: https://docs.rs/syrette/latest/syrette/async_di_container/struct.AsyncDIContainer.html
/// [`Injectable`]: https://docs.rs/syrette/latest/syrette/interfaces/injectable/trait.Injectable.html
/// [`di_container_bind`]: https://docs.rs/syrette/latest/syrette/macro.di_container_bind.html
+#[cfg(not(tarpaulin_include))]
#[proc_macro_attribute]
pub fn injectable(args_stream: TokenStream, impl_stream: TokenStream) -> TokenStream
{
@@ -127,7 +133,7 @@ pub fn injectable(args_stream: TokenStream, impl_stream: TokenStream) -> TokenSt
.find(|flag| flag.flag.to_string().as_str() == "async")
.map_or(false, |flag| flag.is_on.value);
- let injectable_impl: InjectableImpl = parse(impl_stream).unwrap();
+ let injectable_impl: InjectableImpl<Dependency> = parse(impl_stream).unwrap();
let expanded_injectable_impl = injectable_impl.expand(no_doc_hidden, is_async);
@@ -195,6 +201,7 @@ pub fn injectable(args_stream: TokenStream, impl_stream: TokenStream) -> TokenSt
/// [`TransientPtr`]: https://docs.rs/syrette/latest/syrette/ptr/type.TransientPtr.html
#[cfg(feature = "factory")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "factory")))]
+#[cfg(not(tarpaulin_include))]
#[proc_macro_attribute]
pub fn factory(args_stream: TokenStream, type_alias_stream: TokenStream) -> TokenStream
{
@@ -291,9 +298,10 @@ pub fn factory(args_stream: TokenStream, type_alias_stream: TokenStream) -> Toke
///
/// declare_default_factory!(dyn IParser);
/// ```
-#[proc_macro]
#[cfg(feature = "factory")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "factory")))]
+#[cfg(not(tarpaulin_include))]
+#[proc_macro]
pub fn declare_default_factory(args_stream: TokenStream) -> TokenStream
{
use syn::parse_str;
@@ -364,6 +372,7 @@ pub fn declare_default_factory(args_stream: TokenStream) -> TokenStream
/// #
/// declare_interface!(Ninja -> INinja);
/// ```
+#[cfg(not(tarpaulin_include))]
#[proc_macro]
pub fn declare_interface(input: TokenStream) -> TokenStream
{
@@ -423,6 +432,7 @@ pub fn declare_interface(input: TokenStream) -> TokenStream
/// #
/// # impl INinja for Ninja {}
/// ```
+#[cfg(not(tarpaulin_include))]
#[proc_macro_attribute]
pub fn named(_: TokenStream, _: TokenStream) -> TokenStream
{
diff --git a/macros/src/test_utils.rs b/macros/src/test_utils.rs
new file mode 100644
index 0000000..471dc74
--- /dev/null
+++ b/macros/src/test_utils.rs
@@ -0,0 +1,99 @@
+use proc_macro2::{Ident, TokenStream};
+use syn::punctuated::Punctuated;
+use syn::token::{Colon, Gt, Lt, Paren, RArrow};
+use syn::{
+ AngleBracketedGenericArguments,
+ Attribute,
+ FnArg,
+ GenericArgument,
+ GenericParam,
+ Generics,
+ Pat,
+ PatType,
+ Path,
+ PathArguments,
+ PathSegment,
+ Signature,
+ Type,
+ TypePath,
+};
+
+pub fn create_path(segments: &[PathSegment]) -> Path
+{
+ Path {
+ leading_colon: None,
+ segments: segments.iter().cloned().collect(),
+ }
+}
+
+pub fn create_path_segment(ident: Ident, generic_arg_types: &[Type]) -> PathSegment
+{
+ PathSegment {
+ ident,
+ arguments: if generic_arg_types.is_empty() {
+ PathArguments::None
+ } else {
+ PathArguments::AngleBracketed(AngleBracketedGenericArguments {
+ colon2_token: None,
+ lt_token: Lt::default(),
+ args: generic_arg_types
+ .iter()
+ .map(|generic_arg_type| {
+ GenericArgument::Type(generic_arg_type.clone())
+ })
+ .collect(),
+ gt_token: Gt::default(),
+ })
+ },
+ }
+}
+
+pub fn create_type(path: Path) -> Type
+{
+ Type::Path(TypePath { qself: None, path })
+}
+
+pub fn create_generics<Params>(params: Params) -> Generics
+where
+ Params: IntoIterator<Item = GenericParam>,
+{
+ Generics {
+ lt_token: None,
+ params: Punctuated::from_iter(params),
+ gt_token: None,
+ where_clause: None,
+ }
+}
+
+pub fn create_signature<ArgTypes>(
+ ident: Ident,
+ arg_types: ArgTypes,
+ return_type: Type,
+) -> Signature
+where
+ ArgTypes: IntoIterator<Item = (Type, Vec<Attribute>)>,
+{
+ Signature {
+ constness: None,
+ asyncness: None,
+ unsafety: None,
+ abi: None,
+ fn_token: syn::token::Fn::default(),
+ ident,
+ generics: create_generics(vec![]),
+ paren_token: Paren::default(),
+ inputs: arg_types
+ .into_iter()
+ .map(|(arg_type, attrs)| {
+ FnArg::Typed(PatType {
+ attrs,
+ pat: Box::new(Pat::Verbatim(TokenStream::new())),
+ colon_token: Colon::default(),
+ ty: Box::new(arg_type),
+ })
+ })
+ .collect(),
+ variadic: None,
+ output: syn::ReturnType::Type(RArrow::default(), Box::new(return_type)),
+ }
+}