aboutsummaryrefslogtreecommitdiff
path: root/macros/src/injectable/dependency.rs
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/src/injectable/dependency.rs
parentfdbd28fc18a5d2019132413b6699ff7691968fc2 (diff)
refactor: improve internals of macros & add unit tests
Diffstat (limited to 'macros/src/injectable/dependency.rs')
-rw-r--r--macros/src/injectable/dependency.rs337
1 files changed, 303 insertions, 34 deletions
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());
+ }
}